@markup-canvas/core 1.0.0

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.
Files changed (194) hide show
  1. package/README.md +245 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/lib/MarkupCanvas.d.ts +78 -0
  4. package/dist/lib/canvas/calcVisibleArea.d.ts +10 -0
  5. package/dist/lib/canvas/checkContainerDimensions.d.ts +1 -0
  6. package/dist/lib/canvas/config.d.ts +2 -0
  7. package/dist/lib/canvas/createCanvas.d.ts +2 -0
  8. package/dist/lib/canvas/createCanvasLayers.d.ts +6 -0
  9. package/dist/lib/canvas/getCanvasBounds.d.ts +2 -0
  10. package/dist/lib/canvas/getCanvasMethods.d.ts +12 -0
  11. package/dist/lib/canvas/getEmptyBounds.d.ts +2 -0
  12. package/dist/lib/canvas/index.d.ts +3 -0
  13. package/dist/lib/canvas/moveExistingContent.d.ts +1 -0
  14. package/dist/lib/canvas/setupCanvasContainer.d.ts +1 -0
  15. package/dist/lib/canvas/setupContentLayer.d.ts +1 -0
  16. package/dist/lib/canvas/setupTransformLayer.d.ts +2 -0
  17. package/dist/lib/config/constants.d.ts +2 -0
  18. package/dist/lib/config/createMarkupCanvasConfig.d.ts +2 -0
  19. package/dist/lib/constants.d.ts +7 -0
  20. package/dist/lib/events/EventEmitter.d.ts +7 -0
  21. package/dist/lib/events/constants.d.ts +7 -0
  22. package/dist/lib/events/index.d.ts +6 -0
  23. package/dist/lib/events/keyboard/handleKeyDown.d.ts +4 -0
  24. package/dist/lib/events/keyboard/handleKeyUp.d.ts +6 -0
  25. package/dist/lib/events/keyboard/setupKeyboardEvents.d.ts +2 -0
  26. package/dist/lib/events/keyboard/setupKeyboardNavigation.d.ts +2 -0
  27. package/dist/lib/events/mouse/handleClickToZoom.d.ts +2 -0
  28. package/dist/lib/events/mouse/handleMouseDown.d.ts +11 -0
  29. package/dist/lib/events/mouse/handleMouseLeave.d.ts +5 -0
  30. package/dist/lib/events/mouse/handleMouseMove.d.ts +7 -0
  31. package/dist/lib/events/mouse/handleMouseUp.d.ts +7 -0
  32. package/dist/lib/events/mouse/setupMouseDrag.d.ts +4 -0
  33. package/dist/lib/events/mouse/setupMouseEvents.d.ts +4 -0
  34. package/dist/lib/events/touch/getTouchCenter.d.ts +4 -0
  35. package/dist/lib/events/touch/getTouchDistance.d.ts +1 -0
  36. package/dist/lib/events/touch/handleTouchEnd.d.ts +2 -0
  37. package/dist/lib/events/touch/handleTouchMove.d.ts +2 -0
  38. package/dist/lib/events/touch/handleTouchStart.d.ts +2 -0
  39. package/dist/lib/events/touch/setupTouchEvents.d.ts +2 -0
  40. package/dist/lib/events/trackpad/createTrackpadPanHandler.d.ts +4 -0
  41. package/dist/lib/events/trackpad/detectTrackpadGesture.d.ts +2 -0
  42. package/dist/lib/events/utils/getAdaptiveZoomSpeed.d.ts +2 -0
  43. package/dist/lib/events/utils/resetClickState.d.ts +4 -0
  44. package/dist/lib/events/utils/resetDragState.d.ts +5 -0
  45. package/dist/lib/events/utils/updateCursor.d.ts +2 -0
  46. package/dist/lib/events/wheel/handleWheel.d.ts +2 -0
  47. package/dist/lib/events/wheel/setupWheelEvents.d.ts +2 -0
  48. package/dist/lib/events/wheel/setupWheelHandler.d.ts +2 -0
  49. package/dist/lib/helpers/index.d.ts +6 -0
  50. package/dist/lib/helpers/withClampedZoom.d.ts +2 -0
  51. package/dist/lib/helpers/withDebounce.d.ts +1 -0
  52. package/dist/lib/helpers/withFeatureEnabled.d.ts +2 -0
  53. package/dist/lib/helpers/withRAF.d.ts +4 -0
  54. package/dist/lib/helpers/withRulerCheck.d.ts +18 -0
  55. package/dist/lib/helpers/withRulerOffset.d.ts +3 -0
  56. package/dist/lib/matrix/canvasToContent.d.ts +2 -0
  57. package/dist/lib/matrix/clampZoom.d.ts +2 -0
  58. package/dist/lib/matrix/contentToCanvas.d.ts +2 -0
  59. package/dist/lib/matrix/createMatrix.d.ts +1 -0
  60. package/dist/lib/matrix/createMatrixString.d.ts +1 -0
  61. package/dist/lib/matrix/getZoomToMouseTransform.d.ts +2 -0
  62. package/dist/lib/matrix/index.d.ts +5 -0
  63. package/dist/lib/rulers/RulerElements.d.ts +6 -0
  64. package/dist/lib/rulers/constants.d.ts +19 -0
  65. package/dist/lib/rulers/createCornerBox.d.ts +2 -0
  66. package/dist/lib/rulers/createGridOverlay.d.ts +2 -0
  67. package/dist/lib/rulers/createHorizontalRuler.d.ts +2 -0
  68. package/dist/lib/rulers/createRulerElements.d.ts +3 -0
  69. package/dist/lib/rulers/createRulers.d.ts +2 -0
  70. package/dist/lib/rulers/createVerticalRuler.d.ts +2 -0
  71. package/dist/lib/rulers/index.d.ts +2 -0
  72. package/dist/lib/rulers/setupRulerEvents.d.ts +2 -0
  73. package/dist/lib/rulers/ticks/calculateTickSpacing.d.ts +1 -0
  74. package/dist/lib/rulers/ticks/createHorizontalTick.d.ts +2 -0
  75. package/dist/lib/rulers/ticks/createVerticalTick.d.ts +2 -0
  76. package/dist/lib/rulers/ticks/index.d.ts +3 -0
  77. package/dist/lib/rulers/updateGrid.d.ts +1 -0
  78. package/dist/lib/rulers/updateHorizontalRuler.d.ts +2 -0
  79. package/dist/lib/rulers/updateRulers.d.ts +2 -0
  80. package/dist/lib/rulers/updateVerticalRuler.d.ts +2 -0
  81. package/dist/lib/transform/applyTransform.d.ts +1 -0
  82. package/dist/lib/transform/applyZoomToCanvas.d.ts +2 -0
  83. package/dist/lib/transform/hardware-acceleration.d.ts +1 -0
  84. package/dist/lib/transform/index.d.ts +2 -0
  85. package/dist/lib/transition/disableTransition.d.ts +7 -0
  86. package/dist/lib/transition/enableTransition.d.ts +7 -0
  87. package/dist/lib/transition/index.d.ts +3 -0
  88. package/dist/lib/transition/withTransition.d.ts +2 -0
  89. package/dist/markup-canvas.cjs.js +2000 -0
  90. package/dist/markup-canvas.esm.js +1995 -0
  91. package/dist/markup-canvas.umd.js +2003 -0
  92. package/dist/markup-canvas.umd.min.js +1 -0
  93. package/dist/types/canvas.d.ts +86 -0
  94. package/dist/types/config.d.ts +38 -0
  95. package/dist/types/events.d.ts +33 -0
  96. package/dist/types/index.d.ts +5 -0
  97. package/dist/types/matrix.d.ts +17 -0
  98. package/dist/types/rulers.d.ts +31 -0
  99. package/dist/umd.d.ts +1 -0
  100. package/package.json +56 -0
  101. package/src/index.ts +19 -0
  102. package/src/lib/MarkupCanvas.ts +434 -0
  103. package/src/lib/canvas/calcVisibleArea.ts +20 -0
  104. package/src/lib/canvas/checkContainerDimensions.ts +20 -0
  105. package/src/lib/canvas/config.ts +29 -0
  106. package/src/lib/canvas/createCanvas.ts +61 -0
  107. package/src/lib/canvas/createCanvasLayers.ts +39 -0
  108. package/src/lib/canvas/getCanvasBounds.ts +68 -0
  109. package/src/lib/canvas/getCanvasMethods.ts +104 -0
  110. package/src/lib/canvas/getEmptyBounds.ts +22 -0
  111. package/src/lib/canvas/index.ts +3 -0
  112. package/src/lib/canvas/moveExistingContent.ts +9 -0
  113. package/src/lib/canvas/setupCanvasContainer.ts +22 -0
  114. package/src/lib/canvas/setupContentLayer.ts +6 -0
  115. package/src/lib/canvas/setupTransformLayer.ts +15 -0
  116. package/src/lib/config/constants.ts +56 -0
  117. package/src/lib/config/createMarkupCanvasConfig.ts +56 -0
  118. package/src/lib/constants.ts +16 -0
  119. package/src/lib/events/EventEmitter.ts +34 -0
  120. package/src/lib/events/constants.ts +9 -0
  121. package/src/lib/events/index.ts +6 -0
  122. package/src/lib/events/keyboard/handleKeyDown.ts +18 -0
  123. package/src/lib/events/keyboard/handleKeyUp.ts +28 -0
  124. package/src/lib/events/keyboard/setupKeyboardEvents.ts +114 -0
  125. package/src/lib/events/keyboard/setupKeyboardNavigation.ts +115 -0
  126. package/src/lib/events/mouse/handleClickToZoom.ts +54 -0
  127. package/src/lib/events/mouse/handleMouseDown.ts +45 -0
  128. package/src/lib/events/mouse/handleMouseLeave.ts +18 -0
  129. package/src/lib/events/mouse/handleMouseMove.ts +57 -0
  130. package/src/lib/events/mouse/handleMouseUp.ts +40 -0
  131. package/src/lib/events/mouse/setupMouseDrag.ts +159 -0
  132. package/src/lib/events/mouse/setupMouseEvents.ts +158 -0
  133. package/src/lib/events/touch/getTouchCenter.ts +6 -0
  134. package/src/lib/events/touch/getTouchDistance.ts +5 -0
  135. package/src/lib/events/touch/handleTouchEnd.ts +9 -0
  136. package/src/lib/events/touch/handleTouchMove.ts +58 -0
  137. package/src/lib/events/touch/handleTouchStart.ts +14 -0
  138. package/src/lib/events/touch/setupTouchEvents.ts +40 -0
  139. package/src/lib/events/trackpad/createTrackpadPanHandler.ts +35 -0
  140. package/src/lib/events/trackpad/detectTrackpadGesture.ts +22 -0
  141. package/src/lib/events/utils/getAdaptiveZoomSpeed.ts +21 -0
  142. package/src/lib/events/utils/resetClickState.ts +4 -0
  143. package/src/lib/events/utils/resetDragState.ts +17 -0
  144. package/src/lib/events/utils/updateCursor.ts +20 -0
  145. package/src/lib/events/wheel/handleWheel.ts +67 -0
  146. package/src/lib/events/wheel/setupWheelEvents.ts +24 -0
  147. package/src/lib/events/wheel/setupWheelHandler.ts +24 -0
  148. package/src/lib/helpers/index.ts +12 -0
  149. package/src/lib/helpers/withClampedZoom.ts +7 -0
  150. package/src/lib/helpers/withDebounce.ts +15 -0
  151. package/src/lib/helpers/withFeatureEnabled.ts +8 -0
  152. package/src/lib/helpers/withRAF.ts +38 -0
  153. package/src/lib/helpers/withRulerCheck.ts +52 -0
  154. package/src/lib/helpers/withRulerOffset.ts +14 -0
  155. package/src/lib/matrix/canvasToContent.ts +20 -0
  156. package/src/lib/matrix/clampZoom.ts +5 -0
  157. package/src/lib/matrix/contentToCanvas.ts +20 -0
  158. package/src/lib/matrix/createMatrix.ts +3 -0
  159. package/src/lib/matrix/createMatrixString.ts +3 -0
  160. package/src/lib/matrix/getZoomToMouseTransform.ts +46 -0
  161. package/src/lib/matrix/index.ts +5 -0
  162. package/src/lib/rulers/RulerElements.ts +6 -0
  163. package/src/lib/rulers/constants.ts +23 -0
  164. package/src/lib/rulers/createCornerBox.ts +27 -0
  165. package/src/lib/rulers/createGridOverlay.ts +22 -0
  166. package/src/lib/rulers/createHorizontalRuler.ts +24 -0
  167. package/src/lib/rulers/createRulerElements.ts +27 -0
  168. package/src/lib/rulers/createRulers.ts +94 -0
  169. package/src/lib/rulers/createVerticalRuler.ts +24 -0
  170. package/src/lib/rulers/index.ts +2 -0
  171. package/src/lib/rulers/setupRulerEvents.ts +23 -0
  172. package/src/lib/rulers/ticks/calculateTickSpacing.ts +15 -0
  173. package/src/lib/rulers/ticks/createHorizontalTick.ts +41 -0
  174. package/src/lib/rulers/ticks/createVerticalTick.ts +43 -0
  175. package/src/lib/rulers/ticks/index.ts +3 -0
  176. package/src/lib/rulers/updateGrid.ts +11 -0
  177. package/src/lib/rulers/updateHorizontalRuler.ts +32 -0
  178. package/src/lib/rulers/updateRulers.ts +33 -0
  179. package/src/lib/rulers/updateVerticalRuler.ts +31 -0
  180. package/src/lib/transform/applyTransform.ts +15 -0
  181. package/src/lib/transform/applyZoomToCanvas.ts +7 -0
  182. package/src/lib/transform/hardware-acceleration.ts +11 -0
  183. package/src/lib/transform/index.ts +2 -0
  184. package/src/lib/transition/disableTransition.ts +33 -0
  185. package/src/lib/transition/enableTransition.ts +26 -0
  186. package/src/lib/transition/index.ts +3 -0
  187. package/src/lib/transition/withTransition.ts +13 -0
  188. package/src/types/canvas.ts +89 -0
  189. package/src/types/config.ts +54 -0
  190. package/src/types/events.ts +31 -0
  191. package/src/types/index.ts +28 -0
  192. package/src/types/matrix.ts +19 -0
  193. package/src/types/rulers.ts +35 -0
  194. package/src/umd.ts +1 -0
@@ -0,0 +1,1995 @@
1
+ /**
2
+ * Markup Canvas
3
+ * High-performance markup canvas with zoom and pan capabilities
4
+ * @version 1.0.0
5
+ */
6
+ // Default transform values
7
+ const DEFAULT_ZOOM = 1.0;
8
+ // Validation thresholds
9
+ const ZOOM_CHANGE_THRESHOLD = 0.001;
10
+ // CSS transition values
11
+ const FALLBACK_TRANSITION_DURATION = 0.2;
12
+ // Zoom to fit padding factor
13
+ const ZOOM_FIT_PADDING = 0.9;
14
+ // CSS class names
15
+ const CANVAS_CONTAINER_CLASS = "canvas-container";
16
+ const TRANSFORM_LAYER_CLASS = "transform-layer";
17
+ const CONTENT_LAYER_CLASS = "content-layer";
18
+
19
+ function moveExistingContent(existingContent, contentLayer, transformLayer) {
20
+ existingContent.forEach((child) => {
21
+ if (child !== transformLayer && !child.classList.contains(TRANSFORM_LAYER_CLASS)) {
22
+ contentLayer.appendChild(child);
23
+ }
24
+ });
25
+ }
26
+
27
+ function setupContentLayer(contentLayer) {
28
+ contentLayer.style.position = "relative";
29
+ contentLayer.style.width = "100%";
30
+ contentLayer.style.height = "100%";
31
+ contentLayer.style.pointerEvents = "auto";
32
+ }
33
+
34
+ // Rulers
35
+ const RULER_SIZE = 24;
36
+ const RULER_Z_INDEX = {
37
+ GRID: 100,
38
+ RULERS: 1000,
39
+ CORNER: 1001,
40
+ };
41
+ const TICK_SETTINGS = {
42
+ MAJOR_HEIGHT: 6,
43
+ MINOR_HEIGHT: 4,
44
+ MAJOR_WIDTH: 8,
45
+ MINOR_WIDTH: 4,
46
+ MAJOR_MULTIPLIER: 5,
47
+ LABEL_INTERVAL: 100,
48
+ };
49
+ const GRID_SETTINGS = {
50
+ BASE_SIZE: 100,
51
+ MIN_SIZE: 20,
52
+ MAX_SIZE: 200,
53
+ };
54
+
55
+ // Sets up the transform layer with proper styles and dimensions
56
+ function setupTransformLayer(transformLayer, config) {
57
+ transformLayer.style.position = "absolute";
58
+ const rulerOffset = RULER_SIZE;
59
+ transformLayer.style.top = `${rulerOffset}px`;
60
+ transformLayer.style.left = `${rulerOffset}px`;
61
+ transformLayer.style.width = `${config.width}px`;
62
+ transformLayer.style.height = `${config.height}px`;
63
+ transformLayer.style.transformOrigin = "0 0";
64
+ }
65
+
66
+ function createCanvasLayers(container, config) {
67
+ const existingContent = Array.from(container.children);
68
+ // Create or find transform layer
69
+ let transformLayer = container.querySelector(`.${TRANSFORM_LAYER_CLASS}`);
70
+ if (!transformLayer) {
71
+ transformLayer = document.createElement("div");
72
+ transformLayer.className = TRANSFORM_LAYER_CLASS;
73
+ container.appendChild(transformLayer);
74
+ }
75
+ setupTransformLayer(transformLayer, config);
76
+ // Create or find content layer
77
+ let contentLayer = transformLayer.querySelector(`.${CONTENT_LAYER_CLASS}`);
78
+ if (!contentLayer) {
79
+ contentLayer = document.createElement("div");
80
+ contentLayer.className = CONTENT_LAYER_CLASS;
81
+ transformLayer.appendChild(contentLayer);
82
+ moveExistingContent(existingContent, contentLayer, transformLayer);
83
+ }
84
+ // Set content layer properties
85
+ setupContentLayer(contentLayer);
86
+ return { transformLayer, contentLayer };
87
+ }
88
+
89
+ function canvasToContent(canvasX, canvasY, matrix) {
90
+ if (!matrix?.inverse) {
91
+ return { x: canvasX, y: canvasY };
92
+ }
93
+ try {
94
+ const inverseMatrix = matrix.inverse();
95
+ const point = new DOMPoint(canvasX, canvasY);
96
+ const transformed = point.matrixTransform(inverseMatrix);
97
+ return {
98
+ x: transformed.x,
99
+ y: transformed.y,
100
+ };
101
+ }
102
+ catch (error) {
103
+ console.warn("Canvas to content conversion failed:", error);
104
+ return { x: canvasX, y: canvasY };
105
+ }
106
+ }
107
+
108
+ function clampZoom(scale, config) {
109
+ return Math.max(config.minZoom, Math.min(config.maxZoom, scale));
110
+ }
111
+
112
+ function createMatrix(scale, translateX, translateY) {
113
+ return new DOMMatrix([scale, 0, 0, scale, translateX, translateY]);
114
+ }
115
+
116
+ function getZoomToMouseTransform(mouseX, mouseY, currentTransform, zoomFactor, config) {
117
+ const rulerOffset = config.enableRulers ? -RULER_SIZE : 0;
118
+ const transform = currentTransform || {
119
+ scale: DEFAULT_ZOOM,
120
+ translateX: rulerOffset,
121
+ translateY: rulerOffset,
122
+ };
123
+ const { scale, translateX, translateY } = transform;
124
+ // Calculate new scale with clamping
125
+ const newScale = clampZoom(scale * zoomFactor, config);
126
+ // Early return if zoom didn't change (hit bounds)
127
+ if (Math.abs(newScale - scale) < ZOOM_CHANGE_THRESHOLD) {
128
+ return { scale, translateX, translateY };
129
+ }
130
+ // Convert mouse position to content space
131
+ // Formula: contentPos = (mousePos - translate) / scale
132
+ const contentX = (mouseX - translateX) / scale;
133
+ const contentY = (mouseY - translateY) / scale;
134
+ // Calculate new translation
135
+ // Formula: newTranslate = mousePos - (contentPos * newScale)
136
+ const newTranslateX = mouseX - contentX * newScale;
137
+ const newTranslateY = mouseY - contentY * newScale;
138
+ return {
139
+ scale: newScale,
140
+ translateX: newTranslateX,
141
+ translateY: newTranslateY,
142
+ };
143
+ }
144
+
145
+ function calculateVisibleArea(canvasWidth, canvasHeight, contentWidth, contentHeight, transform) {
146
+ const topLeft = canvasToContent(0, 0, createMatrix(transform.scale, transform.translateX, transform.translateY));
147
+ const bottomRight = canvasToContent(canvasWidth, canvasHeight, createMatrix(transform.scale, transform.translateX, transform.translateY));
148
+ return {
149
+ x: Math.max(0, Math.min(contentWidth, topLeft.x)),
150
+ y: Math.max(0, Math.min(contentHeight, topLeft.y)),
151
+ width: Math.max(0, Math.min(contentWidth - topLeft.x, bottomRight.x - topLeft.x)),
152
+ height: Math.max(0, Math.min(contentHeight - topLeft.y, bottomRight.y - topLeft.y)),
153
+ };
154
+ }
155
+
156
+ function getEmptyBounds() {
157
+ return {
158
+ width: 0,
159
+ height: 0,
160
+ contentWidth: 0,
161
+ contentHeight: 0,
162
+ scale: 1,
163
+ translateX: 0,
164
+ translateY: 0,
165
+ visibleArea: { x: 0, y: 0, width: 0, height: 0 },
166
+ scaledContentWidth: 0,
167
+ scaledContentHeight: 0,
168
+ canPanLeft: false,
169
+ canPanRight: false,
170
+ canPanUp: false,
171
+ canPanDown: false,
172
+ canZoomIn: false,
173
+ canZoomOut: false,
174
+ };
175
+ }
176
+
177
+ function withClampedZoom(config, operation) {
178
+ const clampFunction = (scale) => clampZoom(scale, config);
179
+ return operation(clampFunction);
180
+ }
181
+
182
+ const debounceTimers = new Map();
183
+ function withDebounce(key, delay, operation) {
184
+ const existingTimer = debounceTimers.get(key);
185
+ if (existingTimer) {
186
+ clearTimeout(existingTimer);
187
+ }
188
+ const timer = window.setTimeout(() => {
189
+ operation();
190
+ debounceTimers.delete(key);
191
+ }, delay);
192
+ debounceTimers.set(key, timer);
193
+ }
194
+
195
+ function withFeatureEnabled(config, feature, operation) {
196
+ if (config[feature]) {
197
+ return operation();
198
+ }
199
+ return null;
200
+ }
201
+
202
+ function withRAFThrottle(func) {
203
+ let rafId = null;
204
+ let lastArgs = null;
205
+ const throttled = (...args) => {
206
+ lastArgs = args;
207
+ if (rafId === null) {
208
+ rafId = requestAnimationFrame(() => {
209
+ if (lastArgs) {
210
+ func(...lastArgs);
211
+ }
212
+ rafId = null;
213
+ lastArgs = null;
214
+ });
215
+ }
216
+ };
217
+ throttled.cleanup = () => {
218
+ if (rafId !== null) {
219
+ cancelAnimationFrame(rafId);
220
+ rafId = null;
221
+ lastArgs = null;
222
+ }
223
+ };
224
+ return throttled;
225
+ }
226
+
227
+ function withRulerSize(canvas, operation) {
228
+ const hasRulers = canvas.container.querySelector(".canvas-ruler") !== null;
229
+ const rulerSize = hasRulers ? RULER_SIZE : 0;
230
+ return operation(rulerSize);
231
+ }
232
+ function withRulerOffsets(canvas, x, y, operation) {
233
+ return withRulerSize(canvas, (rulerSize) => {
234
+ const adjustedX = x - rulerSize;
235
+ const adjustedY = y - rulerSize;
236
+ return operation(adjustedX, adjustedY);
237
+ });
238
+ }
239
+ function withRulerOffsetObject(canvas, coords, operation) {
240
+ return withRulerSize(canvas, (rulerSize) => {
241
+ const adjusted = {
242
+ ...coords,
243
+ x: coords.x - rulerSize,
244
+ y: coords.y - rulerSize,
245
+ };
246
+ return operation(adjusted);
247
+ });
248
+ }
249
+
250
+ function withRulerOffset(canvas, x, y, operation) {
251
+ const hasRulers = canvas.container.querySelector(".canvas-ruler") !== null;
252
+ const adjustedX = hasRulers ? x - RULER_SIZE : x;
253
+ const adjustedY = hasRulers ? y - RULER_SIZE : y;
254
+ return operation(adjustedX, adjustedY);
255
+ }
256
+
257
+ const DEFAULT_CONFIG = {
258
+ // Canvas dimensions
259
+ width: 8000,
260
+ height: 8000,
261
+ enableAcceleration: true,
262
+ // Interaction controls
263
+ enableZoom: true,
264
+ enablePan: true,
265
+ enableTouch: true,
266
+ enableKeyboard: true,
267
+ limitKeyboardEventsToCanvas: false,
268
+ // Zoom behavior
269
+ zoomSpeed: 1.5,
270
+ minZoom: 0.05,
271
+ maxZoom: 80,
272
+ enableTransition: true,
273
+ transitionDuration: 0.2,
274
+ enableAdaptiveSpeed: true,
275
+ // Pan behavior
276
+ enableLeftDrag: true,
277
+ enableMiddleDrag: true,
278
+ requireSpaceForMouseDrag: false,
279
+ // Keyboard behavior
280
+ keyboardPanStep: 50,
281
+ keyboardFastMultiplier: 20,
282
+ keyboardZoomStep: 0.2,
283
+ // Click-to-zoom
284
+ enableClickToZoom: true,
285
+ clickZoomLevel: 1.0,
286
+ requireOptionForClickZoom: false,
287
+ // Visual elements
288
+ enableRulers: true,
289
+ enableGrid: true,
290
+ gridColor: "rgba(0, 123, 255, 0.1)",
291
+ // Ruler styling
292
+ rulerBackgroundColor: "rgba(255, 255, 255, 0.95)",
293
+ rulerBorderColor: "#ddd",
294
+ rulerTextColor: "#666",
295
+ rulerMajorTickColor: "#999",
296
+ rulerMinorTickColor: "#ccc",
297
+ rulerFontSize: 10,
298
+ rulerFontFamily: "Monaco, Menlo, monospace",
299
+ rulerUnits: "px",
300
+ // Callbacks
301
+ onTransformUpdate: () => { },
302
+ };
303
+
304
+ function getCanvasBounds(canvas) {
305
+ try {
306
+ const container = canvas.container;
307
+ const config = canvas.config;
308
+ const transform = canvas.transform || {
309
+ scale: 1.0,
310
+ translateX: 0,
311
+ translateY: 0,
312
+ };
313
+ // Get canvas dimensions
314
+ const containerRect = container.getBoundingClientRect();
315
+ const totalWidth = containerRect.width || container.clientWidth || 0;
316
+ const totalHeight = containerRect.height || container.clientHeight || 0;
317
+ // Calculate canvas dimensions accounting for rulers
318
+ const canvasWidth = withRulerSize({ container }, (rulerSize) => Math.max(0, totalWidth - rulerSize));
319
+ const canvasHeight = withRulerSize({ container }, (rulerSize) => Math.max(0, totalHeight - rulerSize));
320
+ // Get content dimensions
321
+ const contentWidth = config.width || DEFAULT_CONFIG.width;
322
+ const contentHeight = config.height || DEFAULT_CONFIG.height;
323
+ // Calculate visible area in content coordinates
324
+ const visibleArea = calculateVisibleArea(canvasWidth, canvasHeight, contentWidth, contentHeight, transform);
325
+ return {
326
+ // Canvas dimensions
327
+ width: canvasWidth,
328
+ height: canvasHeight,
329
+ // Content dimensions
330
+ contentWidth,
331
+ contentHeight,
332
+ // Current transform
333
+ scale: transform.scale,
334
+ translateX: transform.translateX,
335
+ translateY: transform.translateY,
336
+ // Visible area in content coordinates
337
+ visibleArea,
338
+ // Calculated properties
339
+ scaledContentWidth: contentWidth * transform.scale,
340
+ scaledContentHeight: contentHeight * transform.scale,
341
+ // Bounds checking
342
+ canPanLeft: transform.translateX < 0,
343
+ canPanRight: transform.translateX + contentWidth * transform.scale > canvasWidth,
344
+ canPanUp: transform.translateY < 0,
345
+ canPanDown: transform.translateY + contentHeight * transform.scale > canvasHeight,
346
+ // Zoom bounds
347
+ canZoomIn: transform.scale < 3.5,
348
+ canZoomOut: transform.scale > 0.1,
349
+ };
350
+ }
351
+ catch (error) {
352
+ console.error("Failed to calculate canvas bounds:", error);
353
+ return getEmptyBounds();
354
+ }
355
+ }
356
+
357
+ function createMatrixString(matrix) {
358
+ return `matrix3d(${matrix.m11}, ${matrix.m12}, ${matrix.m13}, ${matrix.m14}, ${matrix.m21}, ${matrix.m22}, ${matrix.m23}, ${matrix.m24}, ${matrix.m31}, ${matrix.m32}, ${matrix.m33}, ${matrix.m34}, ${matrix.m41}, ${matrix.m42}, ${matrix.m43}, ${matrix.m44})`;
359
+ }
360
+
361
+ function applyTransform(element, matrix) {
362
+ if (!element?.style || !matrix) {
363
+ return false;
364
+ }
365
+ try {
366
+ element.style.transform = createMatrixString(matrix);
367
+ return true;
368
+ }
369
+ catch (error) {
370
+ console.warn("Transform application failed:", error);
371
+ return false;
372
+ }
373
+ }
374
+
375
+ function enableHardwareAcceleration(element) {
376
+ try {
377
+ // Set CSS properties for hardware acceleration
378
+ element.style.transform = element.style.transform || "translateZ(0)";
379
+ element.style.backfaceVisibility = "hidden";
380
+ return true;
381
+ }
382
+ catch (error) {
383
+ console.error("Failed to enable hardware acceleration:", error);
384
+ return false;
385
+ }
386
+ }
387
+
388
+ function disableTransition(element, config) {
389
+ try {
390
+ if (config.enableTransition) {
391
+ if (window.__markupCanvasTransitionTimeout) {
392
+ clearTimeout(window.__markupCanvasTransitionTimeout);
393
+ window.__markupCanvasTransitionTimeout = undefined;
394
+ }
395
+ const delay = (config.transitionDuration ?? FALLBACK_TRANSITION_DURATION) * 1000;
396
+ withDebounce("disableTransition", delay, () => {
397
+ element.style.transition = "none";
398
+ window.__markupCanvasTransitionTimeout = undefined;
399
+ });
400
+ return true;
401
+ }
402
+ return false;
403
+ }
404
+ catch (error) {
405
+ console.error("Failed to disable transitions:", error);
406
+ return true;
407
+ }
408
+ }
409
+
410
+ function enableTransition(element, config) {
411
+ try {
412
+ if (config.enableTransition) {
413
+ if (window.__markupCanvasTransitionTimeout) {
414
+ clearTimeout(window.__markupCanvasTransitionTimeout);
415
+ window.__markupCanvasTransitionTimeout = undefined;
416
+ }
417
+ element.style.transition = `transform ${config.transitionDuration}s linear`;
418
+ return true;
419
+ }
420
+ return false;
421
+ }
422
+ catch (error) {
423
+ console.error("Failed to enable transitions:", error);
424
+ return false;
425
+ }
426
+ }
427
+
428
+ function withTransition(element, config, operation) {
429
+ enableTransition(element, config);
430
+ try {
431
+ const result = operation();
432
+ return result;
433
+ }
434
+ finally {
435
+ disableTransition(element, config);
436
+ }
437
+ }
438
+
439
+ function getCanvasMethods() {
440
+ return {
441
+ // Utility methods
442
+ getBounds: function () {
443
+ return getCanvasBounds(this);
444
+ },
445
+ // Transform methods
446
+ updateTransform: function (newTransform) {
447
+ this.transform = { ...this.transform, ...newTransform };
448
+ const matrix = createMatrix(this.transform.scale, this.transform.translateX, this.transform.translateY);
449
+ const result = applyTransform(this.transformLayer, matrix);
450
+ withFeatureEnabled(this.config, "onTransformUpdate", () => {
451
+ this.config.onTransformUpdate(this.transform);
452
+ });
453
+ return result;
454
+ },
455
+ // Reset method
456
+ reset: function () {
457
+ const resetTransform = {
458
+ scale: 1.0,
459
+ translateX: 0,
460
+ translateY: 0,
461
+ };
462
+ return this.updateTransform(resetTransform);
463
+ },
464
+ // Handle canvas resize
465
+ handleResize: function () {
466
+ this.container.getBoundingClientRect();
467
+ return true;
468
+ },
469
+ // Set zoom level
470
+ setZoom: function (zoomLevel) {
471
+ const newScale = withClampedZoom(this.config, (clamp) => clamp(zoomLevel));
472
+ return this.updateTransform({ scale: newScale });
473
+ },
474
+ // Convert canvas coordinates to content coordinates
475
+ canvasToContent: function (x, y) {
476
+ const matrix = createMatrix(this.transform.scale, this.transform.translateX, this.transform.translateY);
477
+ return canvasToContent(x, y, matrix);
478
+ },
479
+ // Zoom to a specific point with animation
480
+ zoomToPoint: function (x, y, targetScale) {
481
+ return withTransition(this.transformLayer, this.config, () => {
482
+ const newTransform = getZoomToMouseTransform(x, y, this.transform, targetScale / this.transform.scale, this.config);
483
+ return this.updateTransform(newTransform);
484
+ });
485
+ },
486
+ // Reset view with animation
487
+ resetView: function () {
488
+ return withTransition(this.transformLayer, this.config, () => {
489
+ return withRulerSize(this, (rulerSize) => {
490
+ const resetTransform = {
491
+ scale: 1.0,
492
+ translateX: rulerSize * -1,
493
+ translateY: rulerSize * -1,
494
+ };
495
+ return this.updateTransform(resetTransform);
496
+ });
497
+ });
498
+ },
499
+ // Zoom to fit content in canvas
500
+ zoomToFitContent: function () {
501
+ return withTransition(this.transformLayer, this.config, () => {
502
+ const bounds = this.getBounds();
503
+ const scaleX = bounds.width / this.config.width;
504
+ const scaleY = bounds.height / this.config.height;
505
+ const fitScale = withClampedZoom(this.config, (clamp) => clamp(Math.min(scaleX, scaleY) * ZOOM_FIT_PADDING));
506
+ // Center the content
507
+ const scaledWidth = this.config.width * fitScale;
508
+ const scaledHeight = this.config.height * fitScale;
509
+ const centerX = (bounds.width - scaledWidth) / 2;
510
+ const centerY = (bounds.height - scaledHeight) / 2;
511
+ return this.updateTransform({
512
+ scale: fitScale,
513
+ translateX: centerX,
514
+ translateY: centerY,
515
+ });
516
+ });
517
+ },
518
+ };
519
+ }
520
+
521
+ function checkContainerDimensions(container) {
522
+ const containerRect = container.getBoundingClientRect();
523
+ const computedStyle = getComputedStyle(container);
524
+ if (containerRect.height === 0 && computedStyle.height === "auto") {
525
+ 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.", container);
526
+ }
527
+ if (containerRect.width === 0 && computedStyle.width === "auto") {
528
+ 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.", container);
529
+ }
530
+ }
531
+
532
+ function setupCanvasContainer(container) {
533
+ const currentPosition = getComputedStyle(container).position;
534
+ if (currentPosition === "static") {
535
+ container.style.position = "relative";
536
+ }
537
+ container.style.overflow = "hidden";
538
+ container.style.cursor = "grab";
539
+ container.style.overscrollBehavior = "none";
540
+ if (!container.hasAttribute("tabindex")) {
541
+ container.setAttribute("tabindex", "0");
542
+ }
543
+ checkContainerDimensions(container);
544
+ if (!container.classList.contains(CANVAS_CONTAINER_CLASS)) {
545
+ container.classList.add(CANVAS_CONTAINER_CLASS);
546
+ }
547
+ }
548
+
549
+ // Creates and initializes a canvas with the required DOM structure
550
+ function createCanvas(container, config) {
551
+ if (!container?.appendChild) {
552
+ console.error("Invalid container element provided to createCanvas");
553
+ return null;
554
+ }
555
+ try {
556
+ setupCanvasContainer(container);
557
+ const { transformLayer, contentLayer } = createCanvasLayers(container, config);
558
+ // Enable hardware acceleration if requested
559
+ if (config.enableAcceleration) {
560
+ enableHardwareAcceleration(transformLayer);
561
+ }
562
+ const rulerOffset = config.enableRulers ? -RULER_SIZE : 0;
563
+ const initialTransform = {
564
+ scale: DEFAULT_ZOOM,
565
+ translateX: rulerOffset,
566
+ translateY: rulerOffset,
567
+ };
568
+ // Apply initial transform
569
+ const initialMatrix = createMatrix(initialTransform.scale, initialTransform.translateX, initialTransform.translateY);
570
+ applyTransform(transformLayer, initialMatrix);
571
+ const canvas = {
572
+ // DOM references
573
+ container,
574
+ transformLayer,
575
+ contentLayer,
576
+ // Configuration
577
+ config: config,
578
+ // Current state
579
+ transform: initialTransform,
580
+ // Add all canvas methods
581
+ ...getCanvasMethods(),
582
+ };
583
+ return canvas;
584
+ }
585
+ catch (error) {
586
+ console.error("Failed to create canvas:", error);
587
+ return null;
588
+ }
589
+ }
590
+
591
+ function createMarkupCanvasConfig(options = {}) {
592
+ const config = {
593
+ ...DEFAULT_CONFIG,
594
+ ...options,
595
+ };
596
+ if (typeof config.width !== "number" || config.width <= 0) {
597
+ console.warn("Invalid width, using default");
598
+ config.width = DEFAULT_CONFIG.width;
599
+ }
600
+ if (typeof config.height !== "number" || config.height <= 0) {
601
+ console.warn("Invalid height, using default");
602
+ config.height = DEFAULT_CONFIG.height;
603
+ }
604
+ if (typeof config.zoomSpeed !== "number" || config.zoomSpeed <= 0) {
605
+ console.warn("Invalid zoomSpeed, using default");
606
+ config.zoomSpeed = DEFAULT_CONFIG.zoomSpeed;
607
+ }
608
+ if (typeof config.minZoom !== "number" || config.minZoom <= 0) {
609
+ console.warn("Invalid minZoom, using default");
610
+ config.minZoom = DEFAULT_CONFIG.minZoom;
611
+ }
612
+ if (typeof config.maxZoom !== "number" || config.maxZoom <= config.minZoom) {
613
+ console.warn("Invalid maxZoom, using default");
614
+ config.maxZoom = DEFAULT_CONFIG.maxZoom;
615
+ }
616
+ if (typeof config.keyboardPanStep !== "number" || config.keyboardPanStep <= 0) {
617
+ console.warn("Invalid keyboardPanStep, using default");
618
+ config.keyboardPanStep = DEFAULT_CONFIG.keyboardPanStep;
619
+ }
620
+ if (typeof config.keyboardFastMultiplier !== "number" || config.keyboardFastMultiplier <= 0) {
621
+ console.warn("Invalid keyboardFastMultiplier, using default");
622
+ config.keyboardFastMultiplier = DEFAULT_CONFIG.keyboardFastMultiplier;
623
+ }
624
+ if (typeof config.clickZoomLevel !== "number" || config.clickZoomLevel <= 0) {
625
+ console.warn("Invalid clickZoomLevel, using default");
626
+ config.clickZoomLevel = DEFAULT_CONFIG.clickZoomLevel;
627
+ }
628
+ if (typeof config.rulerFontSize !== "number" || config.rulerFontSize <= 0) {
629
+ console.warn("Invalid rulerFontSize, using default");
630
+ config.rulerFontSize = DEFAULT_CONFIG.rulerFontSize;
631
+ }
632
+ return config;
633
+ }
634
+
635
+ class EventEmitter {
636
+ constructor() {
637
+ this.listeners = new Map();
638
+ }
639
+ on(event, handler) {
640
+ if (!this.listeners.has(event)) {
641
+ this.listeners.set(event, new Set());
642
+ }
643
+ this.listeners.get(event).add(handler);
644
+ }
645
+ off(event, handler) {
646
+ const handlers = this.listeners.get(event);
647
+ if (handlers) {
648
+ handlers.delete(handler);
649
+ }
650
+ }
651
+ emit(event, data) {
652
+ const handlers = this.listeners.get(event);
653
+ if (handlers) {
654
+ handlers.forEach((handler) => {
655
+ try {
656
+ handler(data);
657
+ }
658
+ catch (error) {
659
+ console.error(`Error in event handler for "${String(event)}":`, error);
660
+ }
661
+ });
662
+ }
663
+ }
664
+ removeAllListeners() {
665
+ this.listeners.clear();
666
+ }
667
+ }
668
+
669
+ const REFERENCE_DISPLAY_AREA = 1920 * 1080;
670
+ const TRACKPAD_PINCH_SPEED_FACTOR = 0.05;
671
+ const ADAPTIVE_ZOOM_FACTOR = 1;
672
+ const CLICK_THRESHOLDS = {
673
+ MAX_DURATION: 300,
674
+ MAX_MOVEMENT: 5,
675
+ };
676
+
677
+ function getAdaptiveZoomSpeed(canvas, baseSpeed) {
678
+ if (!canvas?.getBounds) {
679
+ return baseSpeed;
680
+ }
681
+ try {
682
+ const bounds = canvas.getBounds();
683
+ const displayArea = bounds.width * bounds.height;
684
+ const rawScaleFactor = (displayArea / REFERENCE_DISPLAY_AREA) ** ADAPTIVE_ZOOM_FACTOR;
685
+ const adaptiveSpeed = baseSpeed * rawScaleFactor;
686
+ return adaptiveSpeed;
687
+ }
688
+ catch (error) {
689
+ console.warn("Failed to calculate adaptive zoom speed, using base speed:", error);
690
+ return baseSpeed;
691
+ }
692
+ }
693
+
694
+ function setupKeyboardEvents(canvas, config) {
695
+ // Track mouse position
696
+ let lastMouseX = 0;
697
+ let lastMouseY = 0;
698
+ function handleMouseMove(event) {
699
+ const rect = canvas.container.getBoundingClientRect();
700
+ const rawMouseX = event.clientX - rect.left;
701
+ const rawMouseY = event.clientY - rect.top;
702
+ withRulerOffsets(canvas, rawMouseX, rawMouseY, (adjustedX, adjustedY) => {
703
+ lastMouseX = adjustedX;
704
+ lastMouseY = adjustedY;
705
+ });
706
+ }
707
+ function handleKeyDown(event) {
708
+ if (!(event instanceof KeyboardEvent))
709
+ return;
710
+ if (config.limitKeyboardEventsToCanvas && document.activeElement !== canvas.container)
711
+ return;
712
+ const isFastPan = event.shiftKey;
713
+ const panDistance = config.keyboardPanStep * (isFastPan ? config.keyboardFastMultiplier : 1);
714
+ let handled = false;
715
+ const newTransform = {};
716
+ switch (event.key) {
717
+ case "ArrowLeft":
718
+ newTransform.translateX = canvas.transform.translateX + panDistance;
719
+ handled = true;
720
+ break;
721
+ case "ArrowRight":
722
+ newTransform.translateX = canvas.transform.translateX - panDistance;
723
+ handled = true;
724
+ break;
725
+ case "ArrowUp":
726
+ newTransform.translateY = canvas.transform.translateY + panDistance;
727
+ handled = true;
728
+ break;
729
+ case "ArrowDown":
730
+ newTransform.translateY = canvas.transform.translateY - panDistance;
731
+ handled = true;
732
+ break;
733
+ case "=":
734
+ case "+":
735
+ {
736
+ const adaptiveZoomStep = config.enableAdaptiveSpeed
737
+ ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
738
+ : config.keyboardZoomStep;
739
+ newTransform.scale = clampZoom(canvas.transform.scale * (1 + adaptiveZoomStep), config);
740
+ handled = true;
741
+ }
742
+ break;
743
+ case "-":
744
+ {
745
+ const adaptiveZoomStep = config.enableAdaptiveSpeed
746
+ ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
747
+ : config.keyboardZoomStep;
748
+ newTransform.scale = clampZoom(canvas.transform.scale * (1 - adaptiveZoomStep), config);
749
+ handled = true;
750
+ }
751
+ break;
752
+ case "0":
753
+ if (event.metaKey || event.ctrlKey) {
754
+ const targetScale = 1.0;
755
+ const zoomFactor = targetScale / canvas.transform.scale;
756
+ const zoomTransform = getZoomToMouseTransform(lastMouseX, lastMouseY, canvas.transform, zoomFactor, config);
757
+ Object.assign(newTransform, zoomTransform);
758
+ handled = true;
759
+ }
760
+ break;
761
+ case "g":
762
+ case "G":
763
+ if (canvas.toggleGrid) {
764
+ canvas.toggleGrid();
765
+ }
766
+ handled = true;
767
+ break;
768
+ case "r":
769
+ case "R":
770
+ if (!event.metaKey && !event.ctrlKey && !event.altKey && canvas.toggleRulers) {
771
+ canvas.toggleRulers();
772
+ handled = true;
773
+ }
774
+ break;
775
+ }
776
+ if (handled) {
777
+ event.preventDefault();
778
+ if (Object.keys(newTransform).length > 0) {
779
+ canvas.updateTransform(newTransform);
780
+ }
781
+ }
782
+ }
783
+ const keyboardTarget = config.limitKeyboardEventsToCanvas ? canvas.container : document;
784
+ keyboardTarget.addEventListener("keydown", handleKeyDown);
785
+ canvas.container.addEventListener("mousemove", handleMouseMove);
786
+ return () => {
787
+ keyboardTarget.removeEventListener("keydown", handleKeyDown);
788
+ canvas.container.removeEventListener("mousemove", handleMouseMove);
789
+ };
790
+ }
791
+
792
+ function updateCursor(canvas, config, isDragEnabled, isSpacePressed, isDragging) {
793
+ if (!isDragEnabled) {
794
+ canvas.container.style.cursor = "default";
795
+ return;
796
+ }
797
+ if (config.requireSpaceForMouseDrag) {
798
+ canvas.container.style.cursor = isSpacePressed ? "grab" : "default";
799
+ }
800
+ else {
801
+ canvas.container.style.cursor = isDragging ? "grabbing" : "grab";
802
+ }
803
+ }
804
+
805
+ function handleKeyDown(event, canvas, config, isDragEnabled, isDragging, setters) {
806
+ if (config.requireSpaceForMouseDrag && event.key === " ") {
807
+ setters.setIsSpacePressed(true);
808
+ updateCursor(canvas, config, isDragEnabled, true, isDragging);
809
+ }
810
+ }
811
+
812
+ function resetDragState(canvas, config, isDragEnabled, isSpacePressed, setters) {
813
+ setters.setIsDragging(false);
814
+ setters.setDragButton(-1);
815
+ updateCursor(canvas, config, isDragEnabled, isSpacePressed, false);
816
+ }
817
+
818
+ function handleKeyUp(event, canvas, config, isDragEnabled, isDragging, setters) {
819
+ if (config.requireSpaceForMouseDrag && event.key === " ") {
820
+ setters.setIsSpacePressed(false);
821
+ updateCursor(canvas, config, isDragEnabled, false, isDragging);
822
+ // Stop dragging if currently dragging
823
+ if (isDragging) {
824
+ resetDragState(canvas, config, isDragEnabled, false, {
825
+ setIsDragging: setters.setIsDragging,
826
+ setDragButton: setters.setDragButton,
827
+ });
828
+ }
829
+ }
830
+ }
831
+
832
+ function handleMouseDown(event, canvas, config, isDragEnabled, isSpacePressed, setters) {
833
+ const isLeftButton = event.button === 0;
834
+ const isMiddleButton = event.button === 1;
835
+ if (isLeftButton) {
836
+ setters.setMouseDownTime(Date.now());
837
+ setters.setMouseDownX(event.clientX);
838
+ setters.setMouseDownY(event.clientY);
839
+ setters.setHasDragged(false);
840
+ }
841
+ if (!isDragEnabled)
842
+ return;
843
+ // Check if drag is allowed based on configuration
844
+ const canDrag = config.requireSpaceForMouseDrag ? isSpacePressed : true;
845
+ if (canDrag && ((isLeftButton && config.enableLeftDrag) || (isMiddleButton && config.enableMiddleDrag))) {
846
+ event.preventDefault();
847
+ // Don't set isDragging to true yet - wait for mouse move
848
+ setters.setDragButton(event.button);
849
+ setters.setLastMouseX(event.clientX);
850
+ setters.setLastMouseY(event.clientY);
851
+ updateCursor(canvas, config, isDragEnabled, isSpacePressed, false); // ← Changed to false
852
+ }
853
+ }
854
+
855
+ function handleMouseLeave(canvas, config, isDragEnabled, isSpacePressed, isDragging, setters) {
856
+ if (isDragging) {
857
+ resetDragState(canvas, config, isDragEnabled, isSpacePressed, setters);
858
+ }
859
+ }
860
+
861
+ function handleMouseMove(event, canvas, isDragEnabled, isDragging, mouseDownTime, mouseDownX, mouseDownY, lastMouseX, lastMouseY, setters) {
862
+ if (mouseDownTime > 0) {
863
+ const deltaX = Math.abs(event.clientX - mouseDownX);
864
+ const deltaY = Math.abs(event.clientY - mouseDownY);
865
+ if (deltaX > CLICK_THRESHOLDS.MAX_MOVEMENT || deltaY > CLICK_THRESHOLDS.MAX_MOVEMENT) {
866
+ setters.setHasDragged(true);
867
+ if (!isDragging && isDragEnabled) {
868
+ setters.setIsDragging(true);
869
+ }
870
+ }
871
+ }
872
+ if (!isDragging || !isDragEnabled)
873
+ return;
874
+ event.preventDefault();
875
+ const handleMouseMoveThrottled = withRAFThrottle((...args) => {
876
+ const moveEvent = args[0];
877
+ if (!isDragging || !isDragEnabled)
878
+ return;
879
+ const deltaX = moveEvent.clientX - lastMouseX;
880
+ const deltaY = moveEvent.clientY - lastMouseY;
881
+ const newTransform = {
882
+ translateX: canvas.transform.translateX + deltaX,
883
+ translateY: canvas.transform.translateY + deltaY,
884
+ };
885
+ canvas.updateTransform(newTransform);
886
+ setters.setLastMouseX(moveEvent.clientX);
887
+ setters.setLastMouseY(moveEvent.clientY);
888
+ });
889
+ handleMouseMoveThrottled(event);
890
+ }
891
+
892
+ function handleClickToZoom(event, canvas, config, mouseDownTime, hasDragged, isDragging) {
893
+ const clickDuration = Date.now() - mouseDownTime;
894
+ // Check if Option/Alt key is required and pressed
895
+ const optionKeyPressed = event.altKey;
896
+ const shouldZoom = config.requireOptionForClickZoom ? optionKeyPressed : true;
897
+ if (clickDuration < CLICK_THRESHOLDS.MAX_DURATION && !hasDragged && !isDragging && shouldZoom) {
898
+ event.preventDefault();
899
+ const rect = canvas.container.getBoundingClientRect();
900
+ const rawClickX = event.clientX - rect.left;
901
+ const rawClickY = event.clientY - rect.top;
902
+ const { clickX, clickY } = withRulerOffset(canvas, rawClickX, rawClickY, (adjustedX, adjustedY) => ({
903
+ clickX: adjustedX,
904
+ clickY: adjustedY,
905
+ }));
906
+ // Convert canvas coordinates to content coordinates at current scale
907
+ const contentCoords = canvas.canvasToContent(clickX, clickY);
908
+ // Calculate the center of the canvas
909
+ const canvasCenterX = rect.width / 2;
910
+ const canvasCenterY = rect.height / 2;
911
+ const newScale = config.clickZoomLevel;
912
+ const newTranslateX = canvasCenterX - contentCoords.x * newScale;
913
+ const newTranslateY = canvasCenterY - contentCoords.y * newScale;
914
+ const newTransform = {
915
+ scale: newScale,
916
+ translateX: newTranslateX,
917
+ translateY: newTranslateY,
918
+ };
919
+ withTransition(canvas.transformLayer, canvas.config, () => {
920
+ canvas.updateTransform(newTransform);
921
+ });
922
+ }
923
+ }
924
+
925
+ function resetClickState(setters) {
926
+ setters.setMouseDownTime(0);
927
+ setters.setHasDragged(false);
928
+ }
929
+
930
+ function handleMouseUp(event, canvas, config, isDragEnabled, isSpacePressed, isDragging, dragButton, mouseDownTime, hasDragged, setters) {
931
+ if (isDragging && event.button === dragButton) {
932
+ resetDragState(canvas, config, isDragEnabled, isSpacePressed, {
933
+ setIsDragging: setters.setIsDragging,
934
+ setDragButton: setters.setDragButton,
935
+ });
936
+ }
937
+ if (isDragEnabled && event.button === 0 && config.enableClickToZoom && mouseDownTime > 0) {
938
+ handleClickToZoom(event, canvas, config, mouseDownTime, hasDragged, isDragging);
939
+ }
940
+ if (event.button === 0) {
941
+ resetClickState({
942
+ setMouseDownTime: setters.setMouseDownTime,
943
+ setHasDragged: setters.setHasDragged,
944
+ });
945
+ }
946
+ }
947
+
948
+ function setupMouseEvents(canvas, config, withControls = true) {
949
+ // State management
950
+ let isDragEnabled = true;
951
+ let isDragging = false;
952
+ let lastMouseX = 0;
953
+ let lastMouseY = 0;
954
+ let dragButton = -1;
955
+ let isSpacePressed = false;
956
+ // Click-to-zoom tracking
957
+ let mouseDownTime = 0;
958
+ let mouseDownX = 0;
959
+ let mouseDownY = 0;
960
+ let hasDragged = false;
961
+ // State setters for passing to functions
962
+ const setters = {
963
+ setIsDragging: (value) => {
964
+ isDragging = value;
965
+ },
966
+ setDragButton: (value) => {
967
+ dragButton = value;
968
+ },
969
+ setIsSpacePressed: (value) => {
970
+ isSpacePressed = value;
971
+ },
972
+ setMouseDownTime: (value) => {
973
+ mouseDownTime = value;
974
+ },
975
+ setMouseDownX: (value) => {
976
+ mouseDownX = value;
977
+ },
978
+ setMouseDownY: (value) => {
979
+ mouseDownY = value;
980
+ },
981
+ setHasDragged: (value) => {
982
+ hasDragged = value;
983
+ },
984
+ setLastMouseX: (value) => {
985
+ lastMouseX = value;
986
+ },
987
+ setLastMouseY: (value) => {
988
+ lastMouseY = value;
989
+ },
990
+ };
991
+ // Event handler wrappers
992
+ const keyDownHandler = (event) => {
993
+ handleKeyDown(event, canvas, config, isDragEnabled, isDragging, {
994
+ setIsSpacePressed: setters.setIsSpacePressed,
995
+ });
996
+ };
997
+ const keyUpHandler = (event) => {
998
+ handleKeyUp(event, canvas, config, isDragEnabled, isDragging, {
999
+ setIsSpacePressed: setters.setIsSpacePressed,
1000
+ setIsDragging: setters.setIsDragging,
1001
+ setDragButton: setters.setDragButton,
1002
+ });
1003
+ };
1004
+ const mouseDownHandler = (event) => {
1005
+ handleMouseDown(event, canvas, config, isDragEnabled, isSpacePressed, setters);
1006
+ };
1007
+ const mouseMoveHandler = (event) => {
1008
+ handleMouseMove(event, canvas, isDragEnabled, isDragging, mouseDownTime, mouseDownX, mouseDownY, lastMouseX, lastMouseY, {
1009
+ setHasDragged: setters.setHasDragged,
1010
+ setIsDragging: setters.setIsDragging,
1011
+ setLastMouseX: setters.setLastMouseX,
1012
+ setLastMouseY: setters.setLastMouseY,
1013
+ });
1014
+ };
1015
+ const mouseUpHandler = (event) => {
1016
+ handleMouseUp(event, canvas, config, isDragEnabled, isSpacePressed, isDragging, dragButton, mouseDownTime, hasDragged, {
1017
+ setIsDragging: setters.setIsDragging,
1018
+ setDragButton: setters.setDragButton,
1019
+ setMouseDownTime: setters.setMouseDownTime,
1020
+ setHasDragged: setters.setHasDragged,
1021
+ });
1022
+ };
1023
+ const mouseLeaveHandler = () => {
1024
+ handleMouseLeave(canvas, config, isDragEnabled, isSpacePressed, isDragging, {
1025
+ setIsDragging: setters.setIsDragging,
1026
+ setDragButton: setters.setDragButton,
1027
+ });
1028
+ };
1029
+ // Set up event listeners
1030
+ canvas.container.addEventListener("mousedown", mouseDownHandler);
1031
+ document.addEventListener("mousemove", mouseMoveHandler);
1032
+ document.addEventListener("mouseup", mouseUpHandler);
1033
+ canvas.container.addEventListener("mouseleave", mouseLeaveHandler);
1034
+ if (config.requireSpaceForMouseDrag) {
1035
+ document.addEventListener("keydown", keyDownHandler);
1036
+ document.addEventListener("keyup", keyUpHandler);
1037
+ }
1038
+ updateCursor(canvas, config, isDragEnabled, isSpacePressed, isDragging);
1039
+ const cleanup = () => {
1040
+ canvas.container.removeEventListener("mousedown", mouseDownHandler);
1041
+ document.removeEventListener("mousemove", mouseMoveHandler);
1042
+ document.removeEventListener("mouseup", mouseUpHandler);
1043
+ canvas.container.removeEventListener("mouseleave", mouseLeaveHandler);
1044
+ if (config.requireSpaceForMouseDrag) {
1045
+ document.removeEventListener("keydown", keyDownHandler);
1046
+ document.removeEventListener("keyup", keyUpHandler);
1047
+ }
1048
+ };
1049
+ if (withControls) {
1050
+ return {
1051
+ cleanup,
1052
+ enable: () => {
1053
+ isDragEnabled = true;
1054
+ updateCursor(canvas, config, isDragEnabled, isSpacePressed, isDragging);
1055
+ return true;
1056
+ },
1057
+ disable: () => {
1058
+ isDragEnabled = false;
1059
+ // Stop any current dragging
1060
+ if (isDragging) {
1061
+ resetDragState(canvas, config, isDragEnabled, isSpacePressed, {
1062
+ setIsDragging: setters.setIsDragging,
1063
+ setDragButton: setters.setDragButton,
1064
+ });
1065
+ }
1066
+ updateCursor(canvas, config, isDragEnabled, isSpacePressed, isDragging);
1067
+ return true;
1068
+ },
1069
+ isEnabled: () => isDragEnabled,
1070
+ };
1071
+ }
1072
+ return cleanup;
1073
+ }
1074
+
1075
+ function handleTouchEnd(event, touchState) {
1076
+ touchState.touches = Array.from(event.touches);
1077
+ if (touchState.touches.length < 2) {
1078
+ touchState.lastDistance = 0;
1079
+ }
1080
+ }
1081
+
1082
+ function getTouchCenter(touch1, touch2) {
1083
+ return {
1084
+ x: (touch1.clientX + touch2.clientX) / 2,
1085
+ y: (touch1.clientY + touch2.clientY) / 2,
1086
+ };
1087
+ }
1088
+
1089
+ function getTouchDistance(touch1, touch2) {
1090
+ const dx = touch1.clientX - touch2.clientX;
1091
+ const dy = touch1.clientY - touch2.clientY;
1092
+ return Math.sqrt(dx * dx + dy * dy);
1093
+ }
1094
+
1095
+ function applyZoomToCanvas(canvas, rawZoomFactor, centerX, centerY) {
1096
+ const newTransform = getZoomToMouseTransform(centerX, centerY, canvas.transform, rawZoomFactor, canvas.config);
1097
+ return canvas.updateTransform(newTransform);
1098
+ }
1099
+
1100
+ function handleTouchMove(event, canvas, touchState) {
1101
+ event.preventDefault();
1102
+ const currentTouches = Array.from(event.touches);
1103
+ // Enhanced RAF-throttled touch move handler for smooth gesture performance
1104
+ const handleTouchMoveThrottled = withRAFThrottle((...args) => {
1105
+ const touches = args[0];
1106
+ if (touches.length === 1) {
1107
+ // Single touch pan
1108
+ if (touchState.touches.length === 1) {
1109
+ const deltaX = touches[0].clientX - touchState.touches[0].clientX;
1110
+ const deltaY = touches[0].clientY - touchState.touches[0].clientY;
1111
+ const newTransform = {
1112
+ translateX: canvas.transform.translateX + deltaX,
1113
+ translateY: canvas.transform.translateY + deltaY,
1114
+ };
1115
+ canvas.updateTransform(newTransform);
1116
+ }
1117
+ }
1118
+ else if (touches.length === 2) {
1119
+ // Two finger pinch zoom
1120
+ const currentDistance = getTouchDistance(touches[0], touches[1]);
1121
+ const currentCenter = getTouchCenter(touches[0], touches[1]);
1122
+ if (touchState.lastDistance > 0) {
1123
+ const rawZoomFactor = currentDistance / touchState.lastDistance;
1124
+ // Get center relative to canvas content area (accounting for rulers)
1125
+ const rect = canvas.container.getBoundingClientRect();
1126
+ let centerX = currentCenter.x - rect.left;
1127
+ let centerY = currentCenter.y - rect.top;
1128
+ // Account for ruler offset if rulers are present
1129
+ const adjustedCenter = withRulerOffsetObject(canvas, { x: centerX, y: centerY }, (adjusted) => adjusted);
1130
+ centerX = adjustedCenter.x;
1131
+ centerY = adjustedCenter.y;
1132
+ // Touch zoom uses global transition settings
1133
+ applyZoomToCanvas(canvas, rawZoomFactor, centerX, centerY);
1134
+ }
1135
+ touchState.lastDistance = currentDistance;
1136
+ touchState.lastCenter = currentCenter;
1137
+ }
1138
+ touchState.touches = touches;
1139
+ });
1140
+ handleTouchMoveThrottled(currentTouches);
1141
+ }
1142
+
1143
+ function handleTouchStart(event, touchState) {
1144
+ event.preventDefault();
1145
+ touchState.touches = Array.from(event.touches);
1146
+ if (touchState.touches.length === 2) {
1147
+ touchState.lastDistance = getTouchDistance(touchState.touches[0], touchState.touches[1]);
1148
+ touchState.lastCenter = getTouchCenter(touchState.touches[0], touchState.touches[1]);
1149
+ }
1150
+ }
1151
+
1152
+ function setupTouchEvents(canvas) {
1153
+ const touchState = {
1154
+ touches: [],
1155
+ lastDistance: 0,
1156
+ lastCenter: { },
1157
+ };
1158
+ const touchStartHandler = (event) => {
1159
+ handleTouchStart(event, touchState);
1160
+ };
1161
+ const touchMoveHandler = (event) => {
1162
+ handleTouchMove(event, canvas, touchState);
1163
+ };
1164
+ const touchEndHandler = (event) => {
1165
+ handleTouchEnd(event, touchState);
1166
+ };
1167
+ canvas.container.addEventListener("touchstart", touchStartHandler, {
1168
+ passive: false,
1169
+ });
1170
+ canvas.container.addEventListener("touchmove", touchMoveHandler, {
1171
+ passive: false,
1172
+ });
1173
+ canvas.container.addEventListener("touchend", touchEndHandler, {
1174
+ passive: false,
1175
+ });
1176
+ return () => {
1177
+ canvas.container.removeEventListener("touchstart", touchStartHandler);
1178
+ canvas.container.removeEventListener("touchmove", touchMoveHandler);
1179
+ canvas.container.removeEventListener("touchend", touchEndHandler);
1180
+ };
1181
+ }
1182
+
1183
+ function detectTrackpadGesture(event) {
1184
+ const isZoomIntent = event.ctrlKey || event.metaKey;
1185
+ const isPixelMode = event.deltaMode === 0;
1186
+ const hasSmallDelta = Math.abs(event.deltaY) < 50;
1187
+ const hasFractionalDelta = event.deltaY % 1 !== 0;
1188
+ const hasMultiAxis = Math.abs(event.deltaX) > 0 && Math.abs(event.deltaY) > 0;
1189
+ const trackpadCriteria = [isPixelMode, hasSmallDelta, hasFractionalDelta, hasMultiAxis];
1190
+ const trackpadMatches = trackpadCriteria.filter(Boolean).length;
1191
+ const isTrackpad = trackpadMatches >= 2;
1192
+ return {
1193
+ isTrackpad,
1194
+ isMouseWheel: !isTrackpad,
1195
+ isTrackpadScroll: isTrackpad && !isZoomIntent,
1196
+ isTrackpadPinch: isTrackpad && isZoomIntent,
1197
+ isZoomGesture: isZoomIntent,
1198
+ };
1199
+ }
1200
+
1201
+ const createTrackpadPanHandler = (canvas) => withRAFThrottle((...args) => {
1202
+ const event = args[0];
1203
+ if (!event || !canvas?.updateTransform) {
1204
+ return false;
1205
+ }
1206
+ try {
1207
+ const currentTransform = canvas.transform;
1208
+ // Calculate pan delta based on trackpad scroll
1209
+ const panSensitivity = 1.0;
1210
+ const deltaX = event.deltaX * panSensitivity;
1211
+ const deltaY = event.deltaY * panSensitivity;
1212
+ // Apply pan by adjusting translation
1213
+ const newTransform = {
1214
+ scale: currentTransform.scale,
1215
+ translateX: currentTransform.translateX - deltaX,
1216
+ translateY: currentTransform.translateY - deltaY,
1217
+ };
1218
+ disableTransition(canvas.transformLayer, canvas.config);
1219
+ // Apply the new transform
1220
+ return canvas.updateTransform(newTransform);
1221
+ }
1222
+ catch (error) {
1223
+ console.error("Error handling trackpad pan:", error);
1224
+ return false;
1225
+ }
1226
+ });
1227
+
1228
+ function handleWheel(event, canvas, config) {
1229
+ if (!event || typeof event.deltaY !== "number") {
1230
+ console.warn("Invalid wheel event provided");
1231
+ return false;
1232
+ }
1233
+ if (!canvas?.updateTransform) {
1234
+ console.warn("Invalid canvas provided to handleWheelEvent");
1235
+ return false;
1236
+ }
1237
+ try {
1238
+ event.preventDefault();
1239
+ // Get mouse position
1240
+ const rect = canvas.container.getBoundingClientRect();
1241
+ const rawMouseX = event.clientX - rect.left;
1242
+ const rawMouseY = event.clientY - rect.top;
1243
+ // Account for ruler offset
1244
+ const { mouseX, mouseY } = withRulerOffset(canvas, rawMouseX, rawMouseY, (adjustedX, adjustedY) => ({
1245
+ mouseX: adjustedX,
1246
+ mouseY: adjustedY,
1247
+ }));
1248
+ // Use the standard zoom speed
1249
+ const baseZoomSpeed = config.zoomSpeed;
1250
+ // Simple gesture detection
1251
+ const gestureInfo = detectTrackpadGesture(event);
1252
+ if (!gestureInfo.isZoomGesture) {
1253
+ // Not a zoom gesture, ignore
1254
+ return false;
1255
+ }
1256
+ // Apply display-size adaptive scaling if enabled
1257
+ const currentZoomSpeed = config.enableAdaptiveSpeed ? getAdaptiveZoomSpeed(canvas, baseZoomSpeed) : baseZoomSpeed;
1258
+ // Simple device-based zoom speed adjustment
1259
+ let deviceZoomSpeed = currentZoomSpeed;
1260
+ if (gestureInfo.isTrackpadPinch) {
1261
+ const baseTrackpadSpeed = config.zoomSpeed * TRACKPAD_PINCH_SPEED_FACTOR;
1262
+ deviceZoomSpeed = config.enableAdaptiveSpeed ? getAdaptiveZoomSpeed(canvas, baseTrackpadSpeed) : baseTrackpadSpeed;
1263
+ }
1264
+ // Calculate zoom delta
1265
+ const zoomDirection = event.deltaY < 0 ? 1 : -1;
1266
+ // Use exponential zoom for more natural feel
1267
+ const rawZoomMultiplier = zoomDirection > 0 ? 1 + deviceZoomSpeed : 1 / (1 + deviceZoomSpeed);
1268
+ return applyZoomToCanvas(canvas, rawZoomMultiplier, mouseX, mouseY);
1269
+ }
1270
+ catch (error) {
1271
+ console.error("Error handling wheel event:", error);
1272
+ return false;
1273
+ }
1274
+ }
1275
+
1276
+ function setupWheelEvents(canvas, config) {
1277
+ const trackpadPanHandler = createTrackpadPanHandler(canvas);
1278
+ const wheelHandler = (event) => {
1279
+ const gestureInfo = detectTrackpadGesture(event);
1280
+ if (gestureInfo.isTrackpadScroll) {
1281
+ return trackpadPanHandler(event);
1282
+ }
1283
+ return handleWheel(event, canvas, config);
1284
+ };
1285
+ canvas.container.addEventListener("wheel", wheelHandler, { passive: false });
1286
+ return () => {
1287
+ canvas.container.removeEventListener("wheel", wheelHandler);
1288
+ };
1289
+ }
1290
+
1291
+ function createCornerBox(config) {
1292
+ const corner = document.createElement("div");
1293
+ corner.className = "canvas-ruler corner-box";
1294
+ corner.style.cssText = `
1295
+ position: absolute;
1296
+ top: 0;
1297
+ left: 0;
1298
+ width: ${RULER_SIZE}px;
1299
+ height: ${RULER_SIZE}px;
1300
+ background: ${config.backgroundColor};
1301
+ border-right: 1px solid ${config.borderColor};
1302
+ border-bottom: 1px solid ${config.borderColor};
1303
+ z-index: ${RULER_Z_INDEX.CORNER};
1304
+ display: flex;
1305
+ align-items: center;
1306
+ justify-content: center;
1307
+ font-family: ${config.fontFamily};
1308
+ font-size: ${config.fontSize - 2}px;
1309
+ color: ${config.textColor};
1310
+ pointer-events: none;
1311
+ `;
1312
+ corner.textContent = config.units;
1313
+ return corner;
1314
+ }
1315
+
1316
+ function createGridOverlay(config) {
1317
+ const grid = document.createElement("div");
1318
+ grid.className = "canvas-ruler grid-overlay";
1319
+ grid.style.cssText = `
1320
+ position: absolute;
1321
+ top: ${RULER_SIZE}px;
1322
+ left: ${RULER_SIZE}px;
1323
+ right: 0;
1324
+ bottom: 0;
1325
+ pointer-events: none;
1326
+ z-index: ${RULER_Z_INDEX.GRID};
1327
+ background-image:
1328
+ linear-gradient(${config.gridColor} 1px, transparent 1px),
1329
+ linear-gradient(90deg, ${config.gridColor} 1px, transparent 1px);
1330
+ background-size: 100px 100px;
1331
+ opacity: 0.5;
1332
+ `;
1333
+ return grid;
1334
+ }
1335
+
1336
+ function createHorizontalRuler(config) {
1337
+ const ruler = document.createElement("div");
1338
+ ruler.className = "canvas-ruler horizontal-ruler";
1339
+ ruler.style.cssText = `
1340
+ position: absolute;
1341
+ top: 0;
1342
+ left: ${RULER_SIZE}px;
1343
+ right: 0;
1344
+ height: ${RULER_SIZE}px;
1345
+ background: ${config.backgroundColor};
1346
+ border-bottom: 1px solid ${config.borderColor};
1347
+ border-right: 1px solid ${config.borderColor};
1348
+ z-index: ${RULER_Z_INDEX.RULERS};
1349
+ pointer-events: none;
1350
+ font-family: ${config.fontFamily};
1351
+ font-size: ${config.fontSize}px;
1352
+ color: ${config.textColor};
1353
+ overflow: hidden;
1354
+ `;
1355
+ return ruler;
1356
+ }
1357
+
1358
+ function createVerticalRuler(config) {
1359
+ const ruler = document.createElement("div");
1360
+ ruler.className = "canvas-ruler vertical-ruler";
1361
+ ruler.style.cssText = `
1362
+ position: absolute;
1363
+ top: ${RULER_SIZE}px;
1364
+ left: 0;
1365
+ bottom: 0;
1366
+ width: ${RULER_SIZE}px;
1367
+ background: ${config.backgroundColor};
1368
+ border-right: 1px solid ${config.borderColor};
1369
+ border-bottom: 1px solid ${config.borderColor};
1370
+ z-index: ${RULER_Z_INDEX.RULERS};
1371
+ pointer-events: none;
1372
+ font-family: ${config.fontFamily};
1373
+ font-size: ${config.fontSize}px;
1374
+ color: ${config.textColor};
1375
+ overflow: hidden;
1376
+ `;
1377
+ return ruler;
1378
+ }
1379
+
1380
+ function createRulerElements(container, config) {
1381
+ const horizontalRuler = createHorizontalRuler(config);
1382
+ const verticalRuler = createVerticalRuler(config);
1383
+ const cornerBox = createCornerBox(config);
1384
+ const gridOverlay = config.showGrid ? createGridOverlay(config) : undefined;
1385
+ container.appendChild(horizontalRuler);
1386
+ container.appendChild(verticalRuler);
1387
+ container.appendChild(cornerBox);
1388
+ if (gridOverlay) {
1389
+ container.appendChild(gridOverlay);
1390
+ }
1391
+ return {
1392
+ horizontalRuler,
1393
+ verticalRuler,
1394
+ cornerBox,
1395
+ gridOverlay,
1396
+ };
1397
+ }
1398
+
1399
+ function setupRulerEvents(canvas, updateCallback) {
1400
+ const throttledUpdateCallback = withRAFThrottle(updateCallback);
1401
+ const originalUpdateTransform = canvas.updateTransform;
1402
+ canvas.updateTransform = function (newTransform) {
1403
+ const result = originalUpdateTransform.call(this, newTransform);
1404
+ throttledUpdateCallback();
1405
+ return result;
1406
+ };
1407
+ const resizeHandler = withRAFThrottle(updateCallback);
1408
+ window.addEventListener("resize", resizeHandler);
1409
+ return () => {
1410
+ window.removeEventListener("resize", resizeHandler);
1411
+ canvas.updateTransform = originalUpdateTransform;
1412
+ throttledUpdateCallback.cleanup();
1413
+ resizeHandler.cleanup();
1414
+ };
1415
+ }
1416
+
1417
+ function updateGrid(gridOverlay, scale, translateX, translateY) {
1418
+ let gridSize = GRID_SETTINGS.BASE_SIZE * scale;
1419
+ while (gridSize < GRID_SETTINGS.MIN_SIZE)
1420
+ gridSize *= 2;
1421
+ while (gridSize > GRID_SETTINGS.MAX_SIZE)
1422
+ gridSize /= 2;
1423
+ gridOverlay.style.backgroundSize = `${gridSize}px ${gridSize}px`;
1424
+ gridOverlay.style.backgroundPosition = `${translateX % gridSize}px ${translateY % gridSize}px`;
1425
+ }
1426
+
1427
+ function calculateTickSpacing(contentSize, canvasSize) {
1428
+ const targetTicks = Math.max(5, Math.min(20, canvasSize / 50));
1429
+ const rawSpacing = contentSize / targetTicks;
1430
+ const magnitude = 10 ** Math.floor(Math.log10(rawSpacing));
1431
+ const normalized = rawSpacing / magnitude;
1432
+ let niceSpacing;
1433
+ if (normalized <= 1)
1434
+ niceSpacing = 1;
1435
+ else if (normalized <= 2)
1436
+ niceSpacing = 2;
1437
+ else if (normalized <= 5)
1438
+ niceSpacing = 5;
1439
+ else
1440
+ niceSpacing = 10;
1441
+ return niceSpacing * magnitude;
1442
+ }
1443
+
1444
+ function createHorizontalTick(container, position, pixelPos, tickSpacing, config) {
1445
+ const tick = document.createElement("div");
1446
+ const isMajor = position % (tickSpacing * TICK_SETTINGS.MAJOR_MULTIPLIER) === 0;
1447
+ const tickHeight = isMajor ? TICK_SETTINGS.MAJOR_HEIGHT : TICK_SETTINGS.MINOR_HEIGHT;
1448
+ tick.style.cssText = `
1449
+ position: absolute;
1450
+ left: ${pixelPos}px;
1451
+ bottom: 0;
1452
+ width: 1px;
1453
+ height: ${tickHeight}px;
1454
+ background: ${isMajor ? config.majorTickColor : config.minorTickColor};
1455
+ `;
1456
+ container.appendChild(tick);
1457
+ const shouldShowLabel = isMajor || position % TICK_SETTINGS.LABEL_INTERVAL === 0;
1458
+ if (shouldShowLabel) {
1459
+ const label = document.createElement("div");
1460
+ label.style.cssText = `
1461
+ position: absolute;
1462
+ left: ${pixelPos}px;
1463
+ bottom: ${tickHeight}px;
1464
+ font-size: ${config.fontSize}px;
1465
+ color: ${config.textColor};
1466
+ white-space: nowrap;
1467
+ pointer-events: none;
1468
+ `;
1469
+ label.textContent = Math.round(position).toString();
1470
+ container.appendChild(label);
1471
+ }
1472
+ }
1473
+
1474
+ function updateHorizontalRuler(ruler, contentLeft, contentRight, canvasWidth, scale, config) {
1475
+ const rulerWidth = canvasWidth;
1476
+ const contentWidth = contentRight - contentLeft;
1477
+ const tickSpacing = calculateTickSpacing(contentWidth, rulerWidth);
1478
+ const fragment = document.createDocumentFragment();
1479
+ const startTick = Math.floor(contentLeft / tickSpacing) * tickSpacing;
1480
+ const endTick = Math.ceil(contentRight / tickSpacing) * tickSpacing;
1481
+ for (let pos = startTick; pos <= endTick; pos += tickSpacing) {
1482
+ const pixelPos = (pos - contentLeft) * scale;
1483
+ if (pixelPos >= -50 && pixelPos <= rulerWidth + 50) {
1484
+ createHorizontalTick(fragment, pos, pixelPos, tickSpacing, config);
1485
+ }
1486
+ }
1487
+ ruler.innerHTML = "";
1488
+ ruler.appendChild(fragment);
1489
+ }
1490
+
1491
+ function createVerticalTick(container, position, pixelPos, tickSpacing, config) {
1492
+ const tick = document.createElement("div");
1493
+ const isMajor = position % (tickSpacing * TICK_SETTINGS.MAJOR_MULTIPLIER) === 0;
1494
+ const tickWidth = isMajor ? TICK_SETTINGS.MAJOR_WIDTH : TICK_SETTINGS.MINOR_WIDTH;
1495
+ tick.style.cssText = `
1496
+ position: absolute;
1497
+ top: ${pixelPos}px;
1498
+ right: 0;
1499
+ width: ${tickWidth}px;
1500
+ height: 1px;
1501
+ background: ${isMajor ? config.majorTickColor : config.minorTickColor};
1502
+ `;
1503
+ container.appendChild(tick);
1504
+ const shouldShowLabel = isMajor || position % TICK_SETTINGS.LABEL_INTERVAL === 0;
1505
+ if (shouldShowLabel) {
1506
+ const label = document.createElement("div");
1507
+ label.style.cssText = `
1508
+ position: absolute;
1509
+ top: ${pixelPos - 6}px;
1510
+ right: ${tickWidth + 6}px;
1511
+ font-size: ${config.fontSize}px;
1512
+ color: ${config.textColor};
1513
+ white-space: nowrap;
1514
+ pointer-events: none;
1515
+ transform: rotate(-90deg);
1516
+ transform-origin: right center;
1517
+ `;
1518
+ label.textContent = Math.round(position).toString();
1519
+ container.appendChild(label);
1520
+ }
1521
+ }
1522
+
1523
+ function updateVerticalRuler(ruler, contentTop, contentBottom, canvasHeight, scale, config) {
1524
+ const rulerHeight = canvasHeight;
1525
+ const contentHeight = contentBottom - contentTop;
1526
+ const tickSpacing = calculateTickSpacing(contentHeight, rulerHeight);
1527
+ const fragment = document.createDocumentFragment();
1528
+ const startTick = Math.floor(contentTop / tickSpacing) * tickSpacing;
1529
+ const endTick = Math.ceil(contentBottom / tickSpacing) * tickSpacing;
1530
+ for (let pos = startTick; pos <= endTick; pos += tickSpacing) {
1531
+ const pixelPos = (pos - contentTop) * scale;
1532
+ if (pixelPos >= -50 && pixelPos <= rulerHeight + 50) {
1533
+ createVerticalTick(fragment, pos, pixelPos, tickSpacing, config);
1534
+ }
1535
+ }
1536
+ ruler.innerHTML = "";
1537
+ ruler.appendChild(fragment);
1538
+ }
1539
+
1540
+ function updateRulers(canvas, horizontalRuler, verticalRuler, gridOverlay, config) {
1541
+ const bounds = canvas.getBounds();
1542
+ const scale = bounds.scale || 1;
1543
+ const translateX = bounds.translateX || 0;
1544
+ const translateY = bounds.translateY || 0;
1545
+ const canvasWidth = bounds.width - RULER_SIZE;
1546
+ const canvasHeight = bounds.height - RULER_SIZE;
1547
+ const contentLeft = -translateX / scale;
1548
+ const contentTop = -translateY / scale;
1549
+ const contentRight = contentLeft + canvasWidth / scale;
1550
+ const contentBottom = contentTop + canvasHeight / scale;
1551
+ updateHorizontalRuler(horizontalRuler, contentLeft, contentRight, canvasWidth, scale, config);
1552
+ updateVerticalRuler(verticalRuler, contentTop, contentBottom, canvasHeight, scale, config);
1553
+ if (gridOverlay) {
1554
+ updateGrid(gridOverlay, scale, translateX, translateY);
1555
+ }
1556
+ }
1557
+
1558
+ function createRulers(canvas, config) {
1559
+ if (!canvas?.container) {
1560
+ console.error("Invalid canvas provided to createRulers");
1561
+ return null;
1562
+ }
1563
+ let elements;
1564
+ let cleanupEvents = null;
1565
+ let isDestroyed = false;
1566
+ const rulerOptions = {
1567
+ backgroundColor: config.rulerBackgroundColor,
1568
+ borderColor: config.rulerBorderColor,
1569
+ textColor: config.rulerTextColor,
1570
+ majorTickColor: config.rulerMajorTickColor,
1571
+ minorTickColor: config.rulerMinorTickColor,
1572
+ fontSize: config.rulerFontSize,
1573
+ fontFamily: config.rulerFontFamily,
1574
+ showGrid: config.enableGrid,
1575
+ gridColor: config.gridColor,
1576
+ units: config.rulerUnits,
1577
+ };
1578
+ const safeUpdate = () => {
1579
+ if (isDestroyed || !elements.horizontalRuler || !elements.verticalRuler)
1580
+ return;
1581
+ updateRulers(canvas, elements.horizontalRuler, elements.verticalRuler, elements.gridOverlay, rulerOptions);
1582
+ };
1583
+ try {
1584
+ elements = createRulerElements(canvas.container, rulerOptions);
1585
+ cleanupEvents = setupRulerEvents(canvas, safeUpdate);
1586
+ safeUpdate();
1587
+ return {
1588
+ horizontalRuler: elements.horizontalRuler,
1589
+ verticalRuler: elements.verticalRuler,
1590
+ cornerBox: elements.cornerBox,
1591
+ gridOverlay: elements.gridOverlay,
1592
+ update: safeUpdate,
1593
+ show: () => {
1594
+ if (elements.horizontalRuler)
1595
+ elements.horizontalRuler.style.display = "block";
1596
+ if (elements.verticalRuler)
1597
+ elements.verticalRuler.style.display = "block";
1598
+ if (elements.cornerBox)
1599
+ elements.cornerBox.style.display = "flex";
1600
+ if (elements.gridOverlay)
1601
+ elements.gridOverlay.style.display = "block";
1602
+ },
1603
+ hide: () => {
1604
+ if (elements.horizontalRuler)
1605
+ elements.horizontalRuler.style.display = "none";
1606
+ if (elements.verticalRuler)
1607
+ elements.verticalRuler.style.display = "none";
1608
+ if (elements.cornerBox)
1609
+ elements.cornerBox.style.display = "none";
1610
+ if (elements.gridOverlay)
1611
+ elements.gridOverlay.style.display = "none";
1612
+ },
1613
+ toggleGrid: () => {
1614
+ if (elements.gridOverlay) {
1615
+ const isVisible = elements.gridOverlay.style.display !== "none";
1616
+ elements.gridOverlay.style.display = isVisible ? "none" : "block";
1617
+ }
1618
+ },
1619
+ destroy: () => {
1620
+ isDestroyed = true;
1621
+ if (cleanupEvents) {
1622
+ cleanupEvents();
1623
+ }
1624
+ if (elements.horizontalRuler?.parentNode) {
1625
+ elements.horizontalRuler.parentNode.removeChild(elements.horizontalRuler);
1626
+ }
1627
+ if (elements.verticalRuler?.parentNode) {
1628
+ elements.verticalRuler.parentNode.removeChild(elements.verticalRuler);
1629
+ }
1630
+ if (elements.cornerBox?.parentNode) {
1631
+ elements.cornerBox.parentNode.removeChild(elements.cornerBox);
1632
+ }
1633
+ if (elements.gridOverlay?.parentNode) {
1634
+ elements.gridOverlay.parentNode.removeChild(elements.gridOverlay);
1635
+ }
1636
+ },
1637
+ };
1638
+ }
1639
+ catch (error) {
1640
+ console.error("Failed to create rulers:", error);
1641
+ return null;
1642
+ }
1643
+ }
1644
+
1645
+ class MarkupCanvas {
1646
+ constructor(container, options = {}) {
1647
+ this.cleanupFunctions = [];
1648
+ this.rulers = null;
1649
+ this.dragSetup = null;
1650
+ this._isReady = false;
1651
+ this.listen = new EventEmitter();
1652
+ if (!container) {
1653
+ throw new Error("Container element is required");
1654
+ }
1655
+ this.config = createMarkupCanvasConfig(options);
1656
+ const canvas = createCanvas(container, this.config);
1657
+ if (!canvas) {
1658
+ throw new Error("Failed to create canvas");
1659
+ }
1660
+ this.baseCanvas = canvas;
1661
+ this.setupEventHandlers();
1662
+ this._isReady = true;
1663
+ // Emit ready event
1664
+ this.listen.emit("ready", this);
1665
+ }
1666
+ setupEventHandlers() {
1667
+ try {
1668
+ // Wheel zoom
1669
+ withFeatureEnabled(this.config, "enableZoom", () => {
1670
+ const wheelCleanup = setupWheelEvents(this, this.config);
1671
+ this.cleanupFunctions.push(wheelCleanup);
1672
+ });
1673
+ // Mouse events (drag and click-to-zoom)
1674
+ // Set up mouse events if either pan or click-to-zoom is enabled
1675
+ if (this.config.enablePan || this.config.enableClickToZoom) {
1676
+ this.dragSetup = setupMouseEvents(this, this.config, true);
1677
+ this.cleanupFunctions.push(this.dragSetup.cleanup);
1678
+ }
1679
+ // Keyboard navigation
1680
+ withFeatureEnabled(this.config, "enableKeyboard", () => {
1681
+ const keyboardCleanup = setupKeyboardEvents(this, this.config);
1682
+ this.cleanupFunctions.push(keyboardCleanup);
1683
+ });
1684
+ // Touch events (if enabled)
1685
+ withFeatureEnabled(this.config, "enableTouch", () => {
1686
+ const touchCleanup = setupTouchEvents(this);
1687
+ this.cleanupFunctions.push(touchCleanup);
1688
+ });
1689
+ // Set up rulers and grid
1690
+ withFeatureEnabled(this.config, "enableRulers", () => {
1691
+ this.rulers = createRulers(this.baseCanvas, this.config);
1692
+ this.cleanupFunctions.push(() => {
1693
+ if (this.rulers) {
1694
+ this.rulers.destroy();
1695
+ }
1696
+ });
1697
+ });
1698
+ }
1699
+ catch (error) {
1700
+ console.error("Failed to set up event handlers:", error);
1701
+ this.cleanup();
1702
+ throw error;
1703
+ }
1704
+ }
1705
+ // Base canvas properties and methods
1706
+ get container() {
1707
+ return this.baseCanvas.container;
1708
+ }
1709
+ get transformLayer() {
1710
+ return this.baseCanvas.transformLayer;
1711
+ }
1712
+ get contentLayer() {
1713
+ return this.baseCanvas.contentLayer;
1714
+ }
1715
+ get transform() {
1716
+ return this.baseCanvas.transform;
1717
+ }
1718
+ // State management getters for React integration
1719
+ get isReady() {
1720
+ return this._isReady;
1721
+ }
1722
+ get isTransforming() {
1723
+ return this.dragSetup?.isEnabled() || false;
1724
+ }
1725
+ get visibleBounds() {
1726
+ return this.getVisibleArea();
1727
+ }
1728
+ getBounds() {
1729
+ return this.baseCanvas.getBounds();
1730
+ }
1731
+ updateTransform(newTransform) {
1732
+ const result = this.baseCanvas.updateTransform(newTransform);
1733
+ if (result) {
1734
+ this.emitTransformEvents();
1735
+ }
1736
+ return result;
1737
+ }
1738
+ emitTransformEvents() {
1739
+ const transform = this.baseCanvas.transform;
1740
+ this.listen.emit("transform", transform);
1741
+ this.listen.emit("zoom", transform.scale);
1742
+ this.listen.emit("pan", { x: transform.translateX, y: transform.translateY });
1743
+ }
1744
+ reset() {
1745
+ return this.baseCanvas.reset();
1746
+ }
1747
+ handleResize() {
1748
+ return this.baseCanvas.handleResize();
1749
+ }
1750
+ setZoom(zoomLevel) {
1751
+ return this.baseCanvas.setZoom(zoomLevel);
1752
+ }
1753
+ canvasToContent(x, y) {
1754
+ return this.baseCanvas.canvasToContent(x, y);
1755
+ }
1756
+ zoomToPoint(x, y, targetScale) {
1757
+ return withTransition(this.transformLayer, this.config, () => {
1758
+ const result = this.baseCanvas.zoomToPoint(x, y, targetScale);
1759
+ if (result) {
1760
+ this.emitTransformEvents();
1761
+ }
1762
+ return result;
1763
+ });
1764
+ }
1765
+ resetView() {
1766
+ return withTransition(this.transformLayer, this.config, () => {
1767
+ const result = this.baseCanvas.resetView ? this.baseCanvas.resetView() : false;
1768
+ if (result) {
1769
+ this.emitTransformEvents();
1770
+ }
1771
+ return result;
1772
+ });
1773
+ }
1774
+ zoomToFitContent() {
1775
+ return withTransition(this.transformLayer, this.config, () => {
1776
+ const result = this.baseCanvas.zoomToFitContent();
1777
+ if (result) {
1778
+ this.emitTransformEvents();
1779
+ }
1780
+ return result;
1781
+ });
1782
+ }
1783
+ // Pan methods
1784
+ panLeft(distance) {
1785
+ const panDistance = distance ?? this.config.keyboardPanStep;
1786
+ const newTransform = {
1787
+ translateX: this.baseCanvas.transform.translateX + panDistance,
1788
+ };
1789
+ return this.updateTransform(newTransform);
1790
+ }
1791
+ panRight(distance) {
1792
+ const panDistance = distance ?? this.config.keyboardPanStep;
1793
+ const newTransform = {
1794
+ translateX: this.baseCanvas.transform.translateX - panDistance,
1795
+ };
1796
+ return this.updateTransform(newTransform);
1797
+ }
1798
+ panUp(distance) {
1799
+ const panDistance = distance ?? this.config.keyboardPanStep;
1800
+ const newTransform = {
1801
+ translateY: this.baseCanvas.transform.translateY + panDistance,
1802
+ };
1803
+ return this.updateTransform(newTransform);
1804
+ }
1805
+ panDown(distance) {
1806
+ const panDistance = distance ?? this.config.keyboardPanStep;
1807
+ const newTransform = {
1808
+ translateY: this.baseCanvas.transform.translateY - panDistance,
1809
+ };
1810
+ return this.updateTransform(newTransform);
1811
+ }
1812
+ // Zoom methods
1813
+ zoomIn(factor = 0.1) {
1814
+ return withTransition(this.transformLayer, this.config, () => {
1815
+ return withClampedZoom(this.config, (clamp) => {
1816
+ const newScale = clamp(this.baseCanvas.transform.scale * (1 + factor));
1817
+ const newTransform = {
1818
+ scale: newScale,
1819
+ };
1820
+ return this.updateTransform(newTransform);
1821
+ });
1822
+ });
1823
+ }
1824
+ zoomOut(factor = 0.1) {
1825
+ return withTransition(this.transformLayer, this.config, () => {
1826
+ return withClampedZoom(this.config, (clamp) => {
1827
+ const newScale = clamp(this.baseCanvas.transform.scale * (1 - factor));
1828
+ const newTransform = {
1829
+ scale: newScale,
1830
+ };
1831
+ return this.updateTransform(newTransform);
1832
+ });
1833
+ });
1834
+ }
1835
+ resetZoom() {
1836
+ return this.resetView();
1837
+ }
1838
+ // Mouse drag control methods
1839
+ enableMouseDrag() {
1840
+ return this.dragSetup?.enable() ?? false;
1841
+ }
1842
+ disableMouseDrag() {
1843
+ return this.dragSetup?.disable() ?? false;
1844
+ }
1845
+ isMouseDragEnabled() {
1846
+ return this.dragSetup?.isEnabled() ?? false;
1847
+ }
1848
+ // Grid control methods
1849
+ toggleGrid() {
1850
+ if (this.rulers?.toggleGrid) {
1851
+ this.rulers.toggleGrid();
1852
+ return true;
1853
+ }
1854
+ return false;
1855
+ }
1856
+ showGrid() {
1857
+ if (this.rulers?.gridOverlay) {
1858
+ this.rulers.gridOverlay.style.display = "block";
1859
+ return true;
1860
+ }
1861
+ return false;
1862
+ }
1863
+ hideGrid() {
1864
+ if (this.rulers?.gridOverlay) {
1865
+ this.rulers.gridOverlay.style.display = "none";
1866
+ return true;
1867
+ }
1868
+ return false;
1869
+ }
1870
+ isGridVisible() {
1871
+ if (this.rulers?.gridOverlay) {
1872
+ return this.rulers.gridOverlay.style.display !== "none";
1873
+ }
1874
+ return false;
1875
+ }
1876
+ // Ruler control methods
1877
+ toggleRulers() {
1878
+ if (this.rulers) {
1879
+ const areVisible = this.areRulersVisible();
1880
+ if (areVisible) {
1881
+ this.rulers.hide();
1882
+ }
1883
+ else {
1884
+ this.rulers.show();
1885
+ }
1886
+ return true;
1887
+ }
1888
+ return false;
1889
+ }
1890
+ showRulers() {
1891
+ if (this.rulers) {
1892
+ this.rulers.show();
1893
+ return true;
1894
+ }
1895
+ return false;
1896
+ }
1897
+ hideRulers() {
1898
+ if (this.rulers) {
1899
+ this.rulers.hide();
1900
+ return true;
1901
+ }
1902
+ return false;
1903
+ }
1904
+ areRulersVisible() {
1905
+ if (this.rulers?.horizontalRuler) {
1906
+ return this.rulers.horizontalRuler.style.display !== "none";
1907
+ }
1908
+ return false;
1909
+ }
1910
+ // Utility methods
1911
+ centerContent() {
1912
+ return withTransition(this.transformLayer, this.config, () => {
1913
+ const bounds = this.baseCanvas.getBounds();
1914
+ const centerX = (bounds.width - bounds.contentWidth * this.baseCanvas.transform.scale) / 2;
1915
+ const centerY = (bounds.height - bounds.contentHeight * this.baseCanvas.transform.scale) / 2;
1916
+ return this.updateTransform({
1917
+ translateX: centerX,
1918
+ translateY: centerY,
1919
+ });
1920
+ });
1921
+ }
1922
+ fitToScreen() {
1923
+ return withTransition(this.transformLayer, this.config, () => {
1924
+ const result = this.baseCanvas.zoomToFitContent();
1925
+ if (result) {
1926
+ this.emitTransformEvents();
1927
+ }
1928
+ return result;
1929
+ });
1930
+ }
1931
+ getVisibleArea() {
1932
+ const bounds = this.baseCanvas.getBounds();
1933
+ return bounds.visibleArea;
1934
+ }
1935
+ isPointVisible(x, y) {
1936
+ const visibleArea = this.getVisibleArea();
1937
+ return x >= visibleArea.x && x <= visibleArea.x + visibleArea.width && y >= visibleArea.y && y <= visibleArea.y + visibleArea.height;
1938
+ }
1939
+ scrollToPoint(x, y) {
1940
+ return withTransition(this.transformLayer, this.config, () => {
1941
+ const bounds = this.baseCanvas.getBounds();
1942
+ const centerX = bounds.width / 2;
1943
+ const centerY = bounds.height / 2;
1944
+ // Calculate new translation to center the point
1945
+ const newTranslateX = centerX - x * this.baseCanvas.transform.scale;
1946
+ const newTranslateY = centerY - y * this.baseCanvas.transform.scale;
1947
+ return this.updateTransform({
1948
+ translateX: newTranslateX,
1949
+ translateY: newTranslateY,
1950
+ });
1951
+ });
1952
+ }
1953
+ // Configuration access
1954
+ getConfig() {
1955
+ return { ...this.config };
1956
+ }
1957
+ updateConfig(newConfig) {
1958
+ this.config = createMarkupCanvasConfig({ ...this.config, ...newConfig });
1959
+ }
1960
+ // Cleanup method
1961
+ cleanup() {
1962
+ this.cleanupFunctions.forEach((cleanup) => {
1963
+ try {
1964
+ cleanup();
1965
+ }
1966
+ catch (cleanupError) {
1967
+ console.warn("Error during cleanup:", cleanupError);
1968
+ }
1969
+ });
1970
+ this.cleanupFunctions = [];
1971
+ // Remove all event listeners
1972
+ this.removeAllListeners();
1973
+ }
1974
+ // Event emitter delegation methods
1975
+ on(event, handler) {
1976
+ this.listen.on(event, handler);
1977
+ }
1978
+ off(event, handler) {
1979
+ this.listen.off(event, handler);
1980
+ }
1981
+ emit(event, data) {
1982
+ this.listen.emit(event, data);
1983
+ }
1984
+ removeAllListeners() {
1985
+ this.listen.removeAllListeners();
1986
+ }
1987
+ destroy() {
1988
+ this.cleanup();
1989
+ if (window.__markupCanvasTransitionTimeout) {
1990
+ clearTimeout(window.__markupCanvasTransitionTimeout);
1991
+ }
1992
+ }
1993
+ }
1994
+
1995
+ export { MarkupCanvas, MarkupCanvas as default };