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