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