@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,434 @@
1
+ import { createCanvas } from "@/lib/canvas/index.js";
2
+ import { createMarkupCanvasConfig } from "@/lib/config/createMarkupCanvasConfig.js";
3
+ import { EventEmitter } from "@/lib/events/EventEmitter.js";
4
+ import { setupKeyboardEvents, setupMouseEvents, setupTouchEvents, setupWheelEvents } from "@/lib/events/index.js";
5
+ import { withClampedZoom, withFeatureEnabled } from "@/lib/helpers/index.js";
6
+ import { createRulers } from "@/lib/rulers/index.js";
7
+ import { withTransition } from "@/lib/transition/withTransition.js";
8
+ import type {
9
+ BaseCanvas,
10
+ Canvas,
11
+ CanvasBounds,
12
+ MarkupCanvasConfig,
13
+ MarkupCanvasEvents,
14
+ MouseDragControls,
15
+ Transform,
16
+ } from "@/types/index.js";
17
+
18
+ declare global {
19
+ interface Window {
20
+ __markupCanvasTransitionTimeout?: number;
21
+ }
22
+ }
23
+
24
+ export class MarkupCanvas implements Canvas {
25
+ private baseCanvas: BaseCanvas;
26
+ private cleanupFunctions: (() => void)[] = [];
27
+ private rulers: ReturnType<typeof createRulers> | null = null;
28
+ private dragSetup: MouseDragControls | null = null;
29
+ public config: Required<MarkupCanvasConfig>;
30
+ private _isReady = false;
31
+ private listen = new EventEmitter<MarkupCanvasEvents>();
32
+
33
+ constructor(container: HTMLElement, options: MarkupCanvasConfig = {}) {
34
+ if (!container) {
35
+ throw new Error("Container element is required");
36
+ }
37
+
38
+ this.config = createMarkupCanvasConfig(options);
39
+
40
+ const canvas = createCanvas(container, this.config);
41
+ if (!canvas) {
42
+ throw new Error("Failed to create canvas");
43
+ }
44
+
45
+ this.baseCanvas = canvas;
46
+ this.setupEventHandlers();
47
+ this._isReady = true;
48
+
49
+ // Emit ready event
50
+ this.listen.emit("ready", this);
51
+ }
52
+
53
+ private setupEventHandlers(): void {
54
+ try {
55
+ // Wheel zoom
56
+ withFeatureEnabled(this.config, "enableZoom", () => {
57
+ const wheelCleanup = setupWheelEvents(this, this.config);
58
+ this.cleanupFunctions.push(wheelCleanup);
59
+ });
60
+
61
+ // Mouse events (drag and click-to-zoom)
62
+ // Set up mouse events if either pan or click-to-zoom is enabled
63
+ if (this.config.enablePan || this.config.enableClickToZoom) {
64
+ this.dragSetup = setupMouseEvents(this, this.config, true);
65
+ this.cleanupFunctions.push(this.dragSetup.cleanup);
66
+ }
67
+
68
+ // Keyboard navigation
69
+ withFeatureEnabled(this.config, "enableKeyboard", () => {
70
+ const keyboardCleanup = setupKeyboardEvents(this, this.config);
71
+ this.cleanupFunctions.push(keyboardCleanup);
72
+ });
73
+
74
+ // Touch events (if enabled)
75
+ withFeatureEnabled(this.config, "enableTouch", () => {
76
+ const touchCleanup = setupTouchEvents(this);
77
+ this.cleanupFunctions.push(touchCleanup);
78
+ });
79
+
80
+ // Set up rulers and grid
81
+ withFeatureEnabled(this.config, "enableRulers", () => {
82
+ this.rulers = createRulers(this.baseCanvas, this.config);
83
+ this.cleanupFunctions.push(() => {
84
+ if (this.rulers) {
85
+ this.rulers.destroy();
86
+ }
87
+ });
88
+ });
89
+ } catch (error) {
90
+ console.error("Failed to set up event handlers:", error);
91
+ this.cleanup();
92
+ throw error;
93
+ }
94
+ }
95
+
96
+ // Base canvas properties and methods
97
+ get container(): HTMLElement {
98
+ return this.baseCanvas.container;
99
+ }
100
+
101
+ get transformLayer(): HTMLElement {
102
+ return this.baseCanvas.transformLayer;
103
+ }
104
+
105
+ get contentLayer(): HTMLElement {
106
+ return this.baseCanvas.contentLayer;
107
+ }
108
+
109
+ get transform(): Transform {
110
+ return this.baseCanvas.transform;
111
+ }
112
+
113
+ // State management getters for React integration
114
+ get isReady(): boolean {
115
+ return this._isReady;
116
+ }
117
+
118
+ get isTransforming(): boolean {
119
+ return this.dragSetup?.isEnabled() || false;
120
+ }
121
+
122
+ get visibleBounds(): { x: number; y: number; width: number; height: number } {
123
+ return this.getVisibleArea();
124
+ }
125
+
126
+ getBounds(): CanvasBounds {
127
+ return this.baseCanvas.getBounds();
128
+ }
129
+
130
+ updateTransform(newTransform: Partial<Transform>): boolean {
131
+ const result = this.baseCanvas.updateTransform(newTransform);
132
+ if (result) {
133
+ this.emitTransformEvents();
134
+ }
135
+ return result;
136
+ }
137
+
138
+ private emitTransformEvents(): void {
139
+ const transform = this.baseCanvas.transform;
140
+ this.listen.emit("transform", transform);
141
+ this.listen.emit("zoom", transform.scale);
142
+ this.listen.emit("pan", { x: transform.translateX, y: transform.translateY });
143
+ }
144
+
145
+ reset(): boolean {
146
+ return this.baseCanvas.reset();
147
+ }
148
+
149
+ handleResize(): boolean {
150
+ return this.baseCanvas.handleResize();
151
+ }
152
+
153
+ setZoom(zoomLevel: number): boolean {
154
+ return this.baseCanvas.setZoom(zoomLevel);
155
+ }
156
+
157
+ canvasToContent(x: number, y: number): { x: number; y: number } {
158
+ return this.baseCanvas.canvasToContent(x, y);
159
+ }
160
+
161
+ zoomToPoint(x: number, y: number, targetScale: number): boolean {
162
+ return withTransition(this.transformLayer, this.config, () => {
163
+ const result = this.baseCanvas.zoomToPoint(x, y, targetScale);
164
+ if (result) {
165
+ this.emitTransformEvents();
166
+ }
167
+ return result;
168
+ });
169
+ }
170
+
171
+ resetView(): boolean {
172
+ return withTransition(this.transformLayer, this.config, () => {
173
+ const result = this.baseCanvas.resetView ? this.baseCanvas.resetView() : false;
174
+ if (result) {
175
+ this.emitTransformEvents();
176
+ }
177
+ return result;
178
+ });
179
+ }
180
+
181
+ zoomToFitContent(): boolean {
182
+ return withTransition(this.transformLayer, this.config, () => {
183
+ const result = this.baseCanvas.zoomToFitContent();
184
+ if (result) {
185
+ this.emitTransformEvents();
186
+ }
187
+ return result;
188
+ });
189
+ }
190
+
191
+ // Pan methods
192
+ panLeft(distance?: number): boolean {
193
+ const panDistance = distance ?? this.config.keyboardPanStep;
194
+ const newTransform: Partial<Transform> = {
195
+ translateX: this.baseCanvas.transform.translateX + panDistance,
196
+ };
197
+ return this.updateTransform(newTransform);
198
+ }
199
+
200
+ panRight(distance?: number): boolean {
201
+ const panDistance = distance ?? this.config.keyboardPanStep;
202
+ const newTransform: Partial<Transform> = {
203
+ translateX: this.baseCanvas.transform.translateX - panDistance,
204
+ };
205
+ return this.updateTransform(newTransform);
206
+ }
207
+
208
+ panUp(distance?: number): boolean {
209
+ const panDistance = distance ?? this.config.keyboardPanStep;
210
+ const newTransform: Partial<Transform> = {
211
+ translateY: this.baseCanvas.transform.translateY + panDistance,
212
+ };
213
+ return this.updateTransform(newTransform);
214
+ }
215
+
216
+ panDown(distance?: number): boolean {
217
+ const panDistance = distance ?? this.config.keyboardPanStep;
218
+ const newTransform: Partial<Transform> = {
219
+ translateY: this.baseCanvas.transform.translateY - panDistance,
220
+ };
221
+ return this.updateTransform(newTransform);
222
+ }
223
+
224
+ // Zoom methods
225
+ zoomIn(factor: number = 0.1): boolean {
226
+ return withTransition(this.transformLayer, this.config, () => {
227
+ return withClampedZoom(this.config, (clamp) => {
228
+ const newScale = clamp(this.baseCanvas.transform.scale * (1 + factor));
229
+ const newTransform: Partial<Transform> = {
230
+ scale: newScale,
231
+ };
232
+ return this.updateTransform(newTransform);
233
+ });
234
+ });
235
+ }
236
+
237
+ zoomOut(factor: number = 0.1): boolean {
238
+ return withTransition(this.transformLayer, this.config, () => {
239
+ return withClampedZoom(this.config, (clamp) => {
240
+ const newScale = clamp(this.baseCanvas.transform.scale * (1 - factor));
241
+ const newTransform: Partial<Transform> = {
242
+ scale: newScale,
243
+ };
244
+ return this.updateTransform(newTransform);
245
+ });
246
+ });
247
+ }
248
+
249
+ resetZoom(): boolean {
250
+ return this.resetView();
251
+ }
252
+
253
+ // Mouse drag control methods
254
+ enableMouseDrag(): boolean {
255
+ return this.dragSetup?.enable() ?? false;
256
+ }
257
+
258
+ disableMouseDrag(): boolean {
259
+ return this.dragSetup?.disable() ?? false;
260
+ }
261
+
262
+ isMouseDragEnabled(): boolean {
263
+ return this.dragSetup?.isEnabled() ?? false;
264
+ }
265
+
266
+ // Grid control methods
267
+ toggleGrid(): boolean {
268
+ if (this.rulers?.toggleGrid) {
269
+ this.rulers.toggleGrid();
270
+ return true;
271
+ }
272
+ return false;
273
+ }
274
+
275
+ showGrid(): boolean {
276
+ if (this.rulers?.gridOverlay) {
277
+ this.rulers.gridOverlay.style.display = "block";
278
+ return true;
279
+ }
280
+ return false;
281
+ }
282
+
283
+ hideGrid(): boolean {
284
+ if (this.rulers?.gridOverlay) {
285
+ this.rulers.gridOverlay.style.display = "none";
286
+ return true;
287
+ }
288
+ return false;
289
+ }
290
+
291
+ isGridVisible(): boolean {
292
+ if (this.rulers?.gridOverlay) {
293
+ return this.rulers.gridOverlay.style.display !== "none";
294
+ }
295
+ return false;
296
+ }
297
+
298
+ // Ruler control methods
299
+ toggleRulers(): boolean {
300
+ if (this.rulers) {
301
+ const areVisible = this.areRulersVisible();
302
+ if (areVisible) {
303
+ this.rulers.hide();
304
+ } else {
305
+ this.rulers.show();
306
+ }
307
+ return true;
308
+ }
309
+ return false;
310
+ }
311
+
312
+ showRulers(): boolean {
313
+ if (this.rulers) {
314
+ this.rulers.show();
315
+ return true;
316
+ }
317
+ return false;
318
+ }
319
+
320
+ hideRulers(): boolean {
321
+ if (this.rulers) {
322
+ this.rulers.hide();
323
+ return true;
324
+ }
325
+ return false;
326
+ }
327
+
328
+ areRulersVisible(): boolean {
329
+ if (this.rulers?.horizontalRuler) {
330
+ return this.rulers.horizontalRuler.style.display !== "none";
331
+ }
332
+ return false;
333
+ }
334
+
335
+ // Utility methods
336
+ centerContent(): boolean {
337
+ return withTransition(this.transformLayer, this.config, () => {
338
+ const bounds = this.baseCanvas.getBounds();
339
+ const centerX = (bounds.width - bounds.contentWidth * this.baseCanvas.transform.scale) / 2;
340
+ const centerY = (bounds.height - bounds.contentHeight * this.baseCanvas.transform.scale) / 2;
341
+
342
+ return this.updateTransform({
343
+ translateX: centerX,
344
+ translateY: centerY,
345
+ });
346
+ });
347
+ }
348
+
349
+ fitToScreen(): boolean {
350
+ return withTransition(this.transformLayer, this.config, () => {
351
+ const result = this.baseCanvas.zoomToFitContent();
352
+ if (result) {
353
+ this.emitTransformEvents();
354
+ }
355
+ return result;
356
+ });
357
+ }
358
+
359
+ getVisibleArea(): { x: number; y: number; width: number; height: number } {
360
+ const bounds = this.baseCanvas.getBounds();
361
+ return bounds.visibleArea;
362
+ }
363
+
364
+ isPointVisible(x: number, y: number): boolean {
365
+ const visibleArea = this.getVisibleArea();
366
+ return x >= visibleArea.x && x <= visibleArea.x + visibleArea.width && y >= visibleArea.y && y <= visibleArea.y + visibleArea.height;
367
+ }
368
+
369
+ scrollToPoint(x: number, y: number): boolean {
370
+ return withTransition(this.transformLayer, this.config, () => {
371
+ const bounds = this.baseCanvas.getBounds();
372
+ const centerX = bounds.width / 2;
373
+ const centerY = bounds.height / 2;
374
+
375
+ // Calculate new translation to center the point
376
+ const newTranslateX = centerX - x * this.baseCanvas.transform.scale;
377
+ const newTranslateY = centerY - y * this.baseCanvas.transform.scale;
378
+
379
+ return this.updateTransform({
380
+ translateX: newTranslateX,
381
+ translateY: newTranslateY,
382
+ });
383
+ });
384
+ }
385
+
386
+ // Configuration access
387
+ getConfig(): Required<MarkupCanvasConfig> {
388
+ return { ...this.config };
389
+ }
390
+
391
+ updateConfig(newConfig: Partial<MarkupCanvasConfig>): void {
392
+ this.config = createMarkupCanvasConfig({ ...this.config, ...newConfig });
393
+ }
394
+
395
+ // Cleanup method
396
+ cleanup(): void {
397
+ this.cleanupFunctions.forEach((cleanup) => {
398
+ try {
399
+ cleanup();
400
+ } catch (cleanupError) {
401
+ console.warn("Error during cleanup:", cleanupError);
402
+ }
403
+ });
404
+ this.cleanupFunctions = [];
405
+
406
+ // Remove all event listeners
407
+ this.removeAllListeners();
408
+ }
409
+
410
+ // Event emitter delegation methods
411
+ on<K extends keyof MarkupCanvasEvents>(event: K, handler: (data: MarkupCanvasEvents[K]) => void): void {
412
+ this.listen.on(event, handler);
413
+ }
414
+
415
+ off<K extends keyof MarkupCanvasEvents>(event: K, handler: (data: MarkupCanvasEvents[K]) => void): void {
416
+ this.listen.off(event, handler);
417
+ }
418
+
419
+ emit<K extends keyof MarkupCanvasEvents>(event: K, data: MarkupCanvasEvents[K]): void {
420
+ this.listen.emit(event, data);
421
+ }
422
+
423
+ removeAllListeners(): void {
424
+ this.listen.removeAllListeners();
425
+ }
426
+
427
+ destroy(): void {
428
+ this.cleanup();
429
+
430
+ if (window.__markupCanvasTransitionTimeout) {
431
+ clearTimeout(window.__markupCanvasTransitionTimeout);
432
+ }
433
+ }
434
+ }
@@ -0,0 +1,20 @@
1
+ import { canvasToContent, createMatrix } from "@/lib/matrix";
2
+
3
+ export function calculateVisibleArea(
4
+ canvasWidth: number,
5
+ canvasHeight: number,
6
+ contentWidth: number,
7
+ contentHeight: number,
8
+ transform: { scale: number; translateX: number; translateY: number }
9
+ ) {
10
+ const topLeft = canvasToContent(0, 0, createMatrix(transform.scale, transform.translateX, transform.translateY));
11
+
12
+ const bottomRight = canvasToContent(canvasWidth, canvasHeight, createMatrix(transform.scale, transform.translateX, transform.translateY));
13
+
14
+ return {
15
+ x: Math.max(0, Math.min(contentWidth, topLeft.x)),
16
+ y: Math.max(0, Math.min(contentHeight, topLeft.y)),
17
+ width: Math.max(0, Math.min(contentWidth - topLeft.x, bottomRight.x - topLeft.x)),
18
+ height: Math.max(0, Math.min(contentHeight - topLeft.y, bottomRight.y - topLeft.y)),
19
+ };
20
+ }
@@ -0,0 +1,20 @@
1
+ export function checkContainerDimensions(container: HTMLElement): void {
2
+ const containerRect = container.getBoundingClientRect();
3
+ const computedStyle = getComputedStyle(container);
4
+
5
+ if (containerRect.height === 0 && computedStyle.height === "auto") {
6
+ console.error(
7
+ "MarkupCanvas: Container height is 0. Please set a height on your container element using CSS.",
8
+ "Examples: height: 100vh, height: 500px, or use flexbox/grid layout.",
9
+ container,
10
+ );
11
+ }
12
+
13
+ if (containerRect.width === 0 && computedStyle.width === "auto") {
14
+ console.error(
15
+ "MarkupCanvas: Container width is 0. Please set a width on your container element using CSS.",
16
+ "Examples: width: 100vw, width: 800px, or use flexbox/grid layout.",
17
+ container,
18
+ );
19
+ }
20
+ }
@@ -0,0 +1,29 @@
1
+ import type { CanvasOptions } from "@/types/index.js";
2
+
3
+ const DEFAULT_CANVAS_WIDTH = 8000;
4
+ const DEFAULT_CANVAS_HEIGHT = 8000;
5
+
6
+ // Creates a validated configuration object with defaults
7
+ export function createCanvasConfig(options: CanvasOptions = {}): Required<CanvasOptions> {
8
+ const config: Required<CanvasOptions> = {
9
+ width: DEFAULT_CANVAS_WIDTH,
10
+ height: DEFAULT_CANVAS_HEIGHT,
11
+ enableAcceleration: true,
12
+ enableEventHandling: true,
13
+ onTransformUpdate: () => {},
14
+ ...options,
15
+ };
16
+
17
+ // Validate and fix configuration values
18
+ if (typeof config.width !== "number" || config.width <= 0) {
19
+ console.warn("Invalid width, using default 8000px");
20
+ config.width = DEFAULT_CANVAS_WIDTH;
21
+ }
22
+
23
+ if (typeof config.height !== "number" || config.height <= 0) {
24
+ console.warn("Invalid height, using default 8000px");
25
+ config.height = DEFAULT_CANVAS_HEIGHT;
26
+ }
27
+
28
+ return config;
29
+ }
@@ -0,0 +1,61 @@
1
+ import { createCanvasLayers } from "@/lib/canvas/createCanvasLayers.js";
2
+ import { getCanvasMethods } from "@/lib/canvas/getCanvasMethods.js";
3
+ import { setupCanvasContainer } from "@/lib/canvas/setupCanvasContainer.js";
4
+ import { DEFAULT_ZOOM } from "@/lib/constants.js";
5
+ import { createMatrix } from "@/lib/matrix/createMatrix.js";
6
+ import { applyTransform, enableHardwareAcceleration } from "@/lib/transform/index.js";
7
+ import type { BaseCanvas, MarkupCanvasConfig, Transform } from "@/types/index.js";
8
+ import { RULER_SIZE } from "../rulers/constants";
9
+
10
+ // Creates and initializes a canvas with the required DOM structure
11
+ export function createCanvas(container: HTMLElement, config: Required<MarkupCanvasConfig>): BaseCanvas | null {
12
+ if (!container?.appendChild) {
13
+ console.error("Invalid container element provided to createCanvas");
14
+ return null;
15
+ }
16
+
17
+ try {
18
+ setupCanvasContainer(container);
19
+
20
+ const { transformLayer, contentLayer } = createCanvasLayers(container, config);
21
+
22
+ // Enable hardware acceleration if requested
23
+ if (config.enableAcceleration) {
24
+ enableHardwareAcceleration(transformLayer);
25
+ }
26
+
27
+ const rulerOffset = config.enableRulers ? -RULER_SIZE : 0;
28
+
29
+ const initialTransform: Transform = {
30
+ scale: DEFAULT_ZOOM,
31
+ translateX: rulerOffset,
32
+ translateY: rulerOffset,
33
+ };
34
+
35
+ // Apply initial transform
36
+ const initialMatrix = createMatrix(initialTransform.scale, initialTransform.translateX, initialTransform.translateY);
37
+
38
+ applyTransform(transformLayer, initialMatrix);
39
+
40
+ const canvas: BaseCanvas = {
41
+ // DOM references
42
+ container,
43
+ transformLayer,
44
+ contentLayer,
45
+
46
+ // Configuration
47
+ config: config,
48
+
49
+ // Current state
50
+ transform: initialTransform,
51
+
52
+ // Add all canvas methods
53
+ ...getCanvasMethods(),
54
+ };
55
+
56
+ return canvas;
57
+ } catch (error) {
58
+ console.error("Failed to create canvas:", error);
59
+ return null;
60
+ }
61
+ }
@@ -0,0 +1,39 @@
1
+ import { moveExistingContent } from "@/lib/canvas/moveExistingContent.js";
2
+ import { setupContentLayer } from "@/lib/canvas/setupContentLayer.js";
3
+ import { setupTransformLayer } from "@/lib/canvas/setupTransformLayer.js";
4
+ import { CONTENT_LAYER_CLASS, TRANSFORM_LAYER_CLASS } from "@/lib/constants.js";
5
+ import type { MarkupCanvasConfig } from "@/types/index.js";
6
+
7
+ export interface CanvasLayers {
8
+ transformLayer: HTMLElement;
9
+ contentLayer: HTMLElement;
10
+ }
11
+
12
+ export function createCanvasLayers(container: HTMLElement, config: Required<MarkupCanvasConfig>): CanvasLayers {
13
+ const existingContent = Array.from(container.children);
14
+
15
+ // Create or find transform layer
16
+ let transformLayer = container.querySelector(`.${TRANSFORM_LAYER_CLASS}`) as HTMLElement;
17
+ if (!transformLayer) {
18
+ transformLayer = document.createElement("div");
19
+ transformLayer.className = TRANSFORM_LAYER_CLASS;
20
+ container.appendChild(transformLayer);
21
+ }
22
+
23
+ setupTransformLayer(transformLayer, config);
24
+
25
+ // Create or find content layer
26
+ let contentLayer = transformLayer.querySelector(`.${CONTENT_LAYER_CLASS}`) as HTMLElement;
27
+ if (!contentLayer) {
28
+ contentLayer = document.createElement("div");
29
+ contentLayer.className = CONTENT_LAYER_CLASS;
30
+ transformLayer.appendChild(contentLayer);
31
+
32
+ moveExistingContent(existingContent, contentLayer, transformLayer);
33
+ }
34
+
35
+ // Set content layer properties
36
+ setupContentLayer(contentLayer);
37
+
38
+ return { transformLayer, contentLayer };
39
+ }
@@ -0,0 +1,68 @@
1
+ import { calculateVisibleArea } from "@/lib/canvas/calcVisibleArea.js";
2
+ import { getEmptyBounds } from "@/lib/canvas/getEmptyBounds.js";
3
+ import { withRulerSize } from "@/lib/helpers/index.js";
4
+ import type { BaseCanvas, CanvasBounds } from "@/types/index.js";
5
+ import { DEFAULT_CONFIG } from "../config/constants";
6
+
7
+ export function getCanvasBounds(canvas: BaseCanvas): CanvasBounds {
8
+ try {
9
+ const container = canvas.container;
10
+ const config = canvas.config;
11
+ const transform = canvas.transform || {
12
+ scale: 1.0,
13
+ translateX: 0,
14
+ translateY: 0,
15
+ };
16
+
17
+ // Get canvas dimensions
18
+ const containerRect = container.getBoundingClientRect();
19
+ const totalWidth = containerRect.width || container.clientWidth || 0;
20
+ const totalHeight = containerRect.height || container.clientHeight || 0;
21
+
22
+ // Calculate canvas dimensions accounting for rulers
23
+ const canvasWidth = withRulerSize({ container }, (rulerSize) => Math.max(0, totalWidth - rulerSize));
24
+ const canvasHeight = withRulerSize({ container }, (rulerSize) => Math.max(0, totalHeight - rulerSize));
25
+
26
+ // Get content dimensions
27
+ const contentWidth = config.width || DEFAULT_CONFIG.width;
28
+ const contentHeight = config.height || DEFAULT_CONFIG.height;
29
+
30
+ // Calculate visible area in content coordinates
31
+ const visibleArea = calculateVisibleArea(canvasWidth, canvasHeight, contentWidth, contentHeight, transform);
32
+
33
+ return {
34
+ // Canvas dimensions
35
+ width: canvasWidth,
36
+ height: canvasHeight,
37
+
38
+ // Content dimensions
39
+ contentWidth,
40
+ contentHeight,
41
+
42
+ // Current transform
43
+ scale: transform.scale,
44
+ translateX: transform.translateX,
45
+ translateY: transform.translateY,
46
+
47
+ // Visible area in content coordinates
48
+ visibleArea,
49
+
50
+ // Calculated properties
51
+ scaledContentWidth: contentWidth * transform.scale,
52
+ scaledContentHeight: contentHeight * transform.scale,
53
+
54
+ // Bounds checking
55
+ canPanLeft: transform.translateX < 0,
56
+ canPanRight: transform.translateX + contentWidth * transform.scale > canvasWidth,
57
+ canPanUp: transform.translateY < 0,
58
+ canPanDown: transform.translateY + contentHeight * transform.scale > canvasHeight,
59
+
60
+ // Zoom bounds
61
+ canZoomIn: transform.scale < 3.5,
62
+ canZoomOut: transform.scale > 0.1,
63
+ };
64
+ } catch (error) {
65
+ console.error("Failed to calculate canvas bounds:", error);
66
+ return getEmptyBounds();
67
+ }
68
+ }