@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,104 @@
1
+ import { getCanvasBounds } from "@/lib/canvas/getCanvasBounds.js";
2
+ import { ZOOM_FIT_PADDING } from "@/lib/constants.js";
3
+ import { withClampedZoom, withFeatureEnabled, withRulerSize } from "@/lib/helpers/index.js";
4
+ import { canvasToContent } from "@/lib/matrix/canvasToContent.js";
5
+ import { createMatrix } from "@/lib/matrix/createMatrix.js";
6
+ import { getZoomToMouseTransform } from "@/lib/matrix/getZoomToMouseTransform.js";
7
+ import { applyTransform } from "@/lib/transform/index.js";
8
+ import { withTransition } from "@/lib/transition/index.js";
9
+ import type { BaseCanvas, Transform } from "@/types/index.js";
10
+
11
+ export function getCanvasMethods() {
12
+ return {
13
+ // Utility methods
14
+ getBounds: function (this: BaseCanvas) {
15
+ return getCanvasBounds(this);
16
+ },
17
+
18
+ // Transform methods
19
+ updateTransform: function (this: BaseCanvas, newTransform: Partial<Transform>) {
20
+ this.transform = { ...this.transform, ...newTransform };
21
+ const matrix = createMatrix(this.transform.scale, this.transform.translateX, this.transform.translateY);
22
+ const result = applyTransform(this.transformLayer, matrix);
23
+
24
+ withFeatureEnabled(this.config, "onTransformUpdate", () => {
25
+ this.config.onTransformUpdate!(this.transform);
26
+ });
27
+
28
+ return result;
29
+ },
30
+
31
+ // Reset method
32
+ reset: function (this: BaseCanvas) {
33
+ const resetTransform: Transform = {
34
+ scale: 1.0,
35
+ translateX: 0,
36
+ translateY: 0,
37
+ };
38
+ return this.updateTransform(resetTransform);
39
+ },
40
+
41
+ // Handle canvas resize
42
+ handleResize: function (this: BaseCanvas) {
43
+ const newRect = this.container.getBoundingClientRect();
44
+
45
+ return true;
46
+ },
47
+
48
+ // Set zoom level
49
+ setZoom: function (this: BaseCanvas, zoomLevel: number) {
50
+ const newScale = withClampedZoom(this.config, (clamp) => clamp(zoomLevel));
51
+ return this.updateTransform({ scale: newScale });
52
+ },
53
+
54
+ // Convert canvas coordinates to content coordinates
55
+ canvasToContent: function (this: BaseCanvas, x: number, y: number) {
56
+ const matrix = createMatrix(this.transform.scale, this.transform.translateX, this.transform.translateY);
57
+ return canvasToContent(x, y, matrix);
58
+ },
59
+
60
+ // Zoom to a specific point with animation
61
+ zoomToPoint: function (this: BaseCanvas, x: number, y: number, targetScale: number) {
62
+ return withTransition(this.transformLayer, this.config, () => {
63
+ const newTransform = getZoomToMouseTransform(x, y, this.transform, targetScale / this.transform.scale, this.config);
64
+ return this.updateTransform(newTransform);
65
+ });
66
+ },
67
+
68
+ // Reset view with animation
69
+ resetView: function (this: BaseCanvas) {
70
+ return withTransition(this.transformLayer, this.config, () => {
71
+ return withRulerSize(this, (rulerSize) => {
72
+ const resetTransform: Transform = {
73
+ scale: 1.0,
74
+ translateX: rulerSize * -1,
75
+ translateY: rulerSize * -1,
76
+ };
77
+ return this.updateTransform(resetTransform);
78
+ });
79
+ });
80
+ },
81
+
82
+ // Zoom to fit content in canvas
83
+ zoomToFitContent: function (this: BaseCanvas) {
84
+ return withTransition(this.transformLayer, this.config, () => {
85
+ const bounds = this.getBounds();
86
+ const scaleX = bounds.width / this.config.width;
87
+ const scaleY = bounds.height / this.config.height;
88
+ const fitScale = withClampedZoom(this.config, (clamp) => clamp(Math.min(scaleX, scaleY) * ZOOM_FIT_PADDING));
89
+
90
+ // Center the content
91
+ const scaledWidth = this.config.width * fitScale;
92
+ const scaledHeight = this.config.height * fitScale;
93
+ const centerX = (bounds.width - scaledWidth) / 2;
94
+ const centerY = (bounds.height - scaledHeight) / 2;
95
+
96
+ return this.updateTransform({
97
+ scale: fitScale,
98
+ translateX: centerX,
99
+ translateY: centerY,
100
+ });
101
+ });
102
+ },
103
+ };
104
+ }
@@ -0,0 +1,22 @@
1
+ import type { CanvasBounds } from "@/types";
2
+
3
+ export function getEmptyBounds(): CanvasBounds {
4
+ return {
5
+ width: 0,
6
+ height: 0,
7
+ contentWidth: 0,
8
+ contentHeight: 0,
9
+ scale: 1,
10
+ translateX: 0,
11
+ translateY: 0,
12
+ visibleArea: { x: 0, y: 0, width: 0, height: 0 },
13
+ scaledContentWidth: 0,
14
+ scaledContentHeight: 0,
15
+ canPanLeft: false,
16
+ canPanRight: false,
17
+ canPanUp: false,
18
+ canPanDown: false,
19
+ canZoomIn: false,
20
+ canZoomOut: false,
21
+ };
22
+ }
@@ -0,0 +1,3 @@
1
+ export { createCanvasConfig } from "./config.js";
2
+ export { createCanvas } from "./createCanvas.js";
3
+ export { getCanvasBounds } from "./getCanvasBounds.js";
@@ -0,0 +1,9 @@
1
+ import { TRANSFORM_LAYER_CLASS } from "@/lib/constants";
2
+
3
+ export function moveExistingContent(existingContent: Element[], contentLayer: HTMLElement, transformLayer: HTMLElement): void {
4
+ existingContent.forEach((child) => {
5
+ if (child !== transformLayer && !child.classList.contains(TRANSFORM_LAYER_CLASS)) {
6
+ contentLayer.appendChild(child);
7
+ }
8
+ });
9
+ }
@@ -0,0 +1,22 @@
1
+ import { checkContainerDimensions } from "@/lib/canvas/checkContainerDimensions";
2
+ import { CANVAS_CONTAINER_CLASS } from "@/lib/constants";
3
+
4
+ export function setupCanvasContainer(container: HTMLElement): void {
5
+ const currentPosition = getComputedStyle(container).position;
6
+ if (currentPosition === "static") {
7
+ container.style.position = "relative";
8
+ }
9
+ container.style.overflow = "hidden";
10
+ container.style.cursor = "grab";
11
+ container.style.overscrollBehavior = "none";
12
+
13
+ if (!container.hasAttribute("tabindex")) {
14
+ container.setAttribute("tabindex", "0");
15
+ }
16
+
17
+ checkContainerDimensions(container);
18
+
19
+ if (!container.classList.contains(CANVAS_CONTAINER_CLASS)) {
20
+ container.classList.add(CANVAS_CONTAINER_CLASS);
21
+ }
22
+ }
@@ -0,0 +1,6 @@
1
+ export function setupContentLayer(contentLayer: HTMLElement): void {
2
+ contentLayer.style.position = "relative";
3
+ contentLayer.style.width = "100%";
4
+ contentLayer.style.height = "100%";
5
+ contentLayer.style.pointerEvents = "auto";
6
+ }
@@ -0,0 +1,15 @@
1
+ import { RULER_SIZE } from "@/lib/rulers/constants";
2
+ import type { MarkupCanvasConfig } from "@/types";
3
+
4
+ // Sets up the transform layer with proper styles and dimensions
5
+ export function setupTransformLayer(transformLayer: HTMLElement, config: Required<MarkupCanvasConfig>): void {
6
+ transformLayer.style.position = "absolute";
7
+
8
+ const rulerOffset = RULER_SIZE;
9
+
10
+ transformLayer.style.top = `${rulerOffset}px`;
11
+ transformLayer.style.left = `${rulerOffset}px`;
12
+ transformLayer.style.width = `${config.width}px`;
13
+ transformLayer.style.height = `${config.height}px`;
14
+ transformLayer.style.transformOrigin = "0 0";
15
+ }
@@ -0,0 +1,56 @@
1
+ import type { MarkupCanvasConfig } from "@/types";
2
+
3
+ export const DEFAULT_CONFIG: Required<MarkupCanvasConfig> = {
4
+ // Canvas dimensions
5
+ width: 8000,
6
+ height: 8000,
7
+ enableAcceleration: true,
8
+
9
+ // Interaction controls
10
+ enableZoom: true,
11
+ enablePan: true,
12
+ enableTouch: true,
13
+ enableKeyboard: true,
14
+ limitKeyboardEventsToCanvas: false,
15
+
16
+ // Zoom behavior
17
+ zoomSpeed: 1.5,
18
+ minZoom: 0.05,
19
+ maxZoom: 80,
20
+ enableTransition: true,
21
+ transitionDuration: 0.2,
22
+ enableAdaptiveSpeed: true,
23
+
24
+ // Pan behavior
25
+ enableLeftDrag: true,
26
+ enableMiddleDrag: true,
27
+ requireSpaceForMouseDrag: false,
28
+
29
+ // Keyboard behavior
30
+ keyboardPanStep: 50,
31
+ keyboardFastMultiplier: 20,
32
+ keyboardZoomStep: 0.2,
33
+
34
+ // Click-to-zoom
35
+ enableClickToZoom: true,
36
+ clickZoomLevel: 1.0,
37
+ requireOptionForClickZoom: false,
38
+
39
+ // Visual elements
40
+ enableRulers: true,
41
+ enableGrid: true,
42
+ gridColor: "rgba(0, 123, 255, 0.1)",
43
+
44
+ // Ruler styling
45
+ rulerBackgroundColor: "rgba(255, 255, 255, 0.95)",
46
+ rulerBorderColor: "#ddd",
47
+ rulerTextColor: "#666",
48
+ rulerMajorTickColor: "#999",
49
+ rulerMinorTickColor: "#ccc",
50
+ rulerFontSize: 10,
51
+ rulerFontFamily: "Monaco, Menlo, monospace",
52
+ rulerUnits: "px",
53
+
54
+ // Callbacks
55
+ onTransformUpdate: () => {},
56
+ };
@@ -0,0 +1,56 @@
1
+ import { DEFAULT_CONFIG } from "@/lib/config/constants.js";
2
+ import type { MarkupCanvasConfig } from "@/types/index.js";
3
+
4
+ export function createMarkupCanvasConfig(options: MarkupCanvasConfig = {}): Required<MarkupCanvasConfig> {
5
+ const config: Required<MarkupCanvasConfig> = {
6
+ ...DEFAULT_CONFIG,
7
+ ...options,
8
+ };
9
+
10
+ if (typeof config.width !== "number" || config.width <= 0) {
11
+ console.warn("Invalid width, using default");
12
+ config.width = DEFAULT_CONFIG.width;
13
+ }
14
+
15
+ if (typeof config.height !== "number" || config.height <= 0) {
16
+ console.warn("Invalid height, using default");
17
+ config.height = DEFAULT_CONFIG.height;
18
+ }
19
+
20
+ if (typeof config.zoomSpeed !== "number" || config.zoomSpeed <= 0) {
21
+ console.warn("Invalid zoomSpeed, using default");
22
+ config.zoomSpeed = DEFAULT_CONFIG.zoomSpeed;
23
+ }
24
+
25
+ if (typeof config.minZoom !== "number" || config.minZoom <= 0) {
26
+ console.warn("Invalid minZoom, using default");
27
+ config.minZoom = DEFAULT_CONFIG.minZoom;
28
+ }
29
+
30
+ if (typeof config.maxZoom !== "number" || config.maxZoom <= config.minZoom) {
31
+ console.warn("Invalid maxZoom, using default");
32
+ config.maxZoom = DEFAULT_CONFIG.maxZoom;
33
+ }
34
+
35
+ if (typeof config.keyboardPanStep !== "number" || config.keyboardPanStep <= 0) {
36
+ console.warn("Invalid keyboardPanStep, using default");
37
+ config.keyboardPanStep = DEFAULT_CONFIG.keyboardPanStep;
38
+ }
39
+
40
+ if (typeof config.keyboardFastMultiplier !== "number" || config.keyboardFastMultiplier <= 0) {
41
+ console.warn("Invalid keyboardFastMultiplier, using default");
42
+ config.keyboardFastMultiplier = DEFAULT_CONFIG.keyboardFastMultiplier;
43
+ }
44
+
45
+ if (typeof config.clickZoomLevel !== "number" || config.clickZoomLevel <= 0) {
46
+ console.warn("Invalid clickZoomLevel, using default");
47
+ config.clickZoomLevel = DEFAULT_CONFIG.clickZoomLevel;
48
+ }
49
+
50
+ if (typeof config.rulerFontSize !== "number" || config.rulerFontSize <= 0) {
51
+ console.warn("Invalid rulerFontSize, using default");
52
+ config.rulerFontSize = DEFAULT_CONFIG.rulerFontSize;
53
+ }
54
+
55
+ return config;
56
+ }
@@ -0,0 +1,16 @@
1
+ // Default transform values
2
+ export const DEFAULT_ZOOM = 1.0;
3
+
4
+ // Validation thresholds
5
+ export const ZOOM_CHANGE_THRESHOLD = 0.001;
6
+
7
+ // CSS transition values
8
+ export const FALLBACK_TRANSITION_DURATION = 0.2;
9
+
10
+ // Zoom to fit padding factor
11
+ export const ZOOM_FIT_PADDING = 0.9;
12
+
13
+ // CSS class names
14
+ export const CANVAS_CONTAINER_CLASS = "canvas-container";
15
+ export const TRANSFORM_LAYER_CLASS = "transform-layer";
16
+ export const CONTENT_LAYER_CLASS = "content-layer";
@@ -0,0 +1,34 @@
1
+ export class EventEmitter<Events extends Record<string, unknown>> {
2
+ private listeners: Map<keyof Events, Set<(data: unknown) => void>> = new Map();
3
+
4
+ on<K extends keyof Events>(event: K, handler: (data: Events[K]) => void): void {
5
+ if (!this.listeners.has(event)) {
6
+ this.listeners.set(event, new Set());
7
+ }
8
+ this.listeners.get(event)!.add(handler as (data: unknown) => void);
9
+ }
10
+
11
+ off<K extends keyof Events>(event: K, handler: (data: Events[K]) => void): void {
12
+ const handlers = this.listeners.get(event);
13
+ if (handlers) {
14
+ handlers.delete(handler as (data: unknown) => void);
15
+ }
16
+ }
17
+
18
+ emit<K extends keyof Events>(event: K, data: Events[K]): void {
19
+ const handlers = this.listeners.get(event);
20
+ if (handlers) {
21
+ handlers.forEach((handler) => {
22
+ try {
23
+ handler(data);
24
+ } catch (error) {
25
+ console.error(`Error in event handler for "${String(event)}":`, error);
26
+ }
27
+ });
28
+ }
29
+ }
30
+
31
+ removeAllListeners(): void {
32
+ this.listeners.clear();
33
+ }
34
+ }
@@ -0,0 +1,9 @@
1
+ export const REFERENCE_DISPLAY_AREA = 1920 * 1080;
2
+
3
+ export const TRACKPAD_PINCH_SPEED_FACTOR = 0.05;
4
+ export const ADAPTIVE_ZOOM_FACTOR = 1;
5
+
6
+ export const CLICK_THRESHOLDS = {
7
+ MAX_DURATION: 300,
8
+ MAX_MOVEMENT: 5,
9
+ } as const;
@@ -0,0 +1,6 @@
1
+ export { setupKeyboardEvents } from "./keyboard/setupKeyboardEvents.js";
2
+ export { setupMouseEvents } from "./mouse/setupMouseEvents.js";
3
+ export { setupTouchEvents } from "./touch/setupTouchEvents.js";
4
+ export { detectTrackpadGesture } from "./trackpad/detectTrackpadGesture.js";
5
+ export { getAdaptiveZoomSpeed } from "./utils/getAdaptiveZoomSpeed.js";
6
+ export { setupWheelEvents } from "./wheel/setupWheelEvents.js";
@@ -0,0 +1,18 @@
1
+ import { updateCursor } from "@/lib/events/utils/updateCursor.js";
2
+ import type { Canvas, MarkupCanvasConfig } from "@/types/index.js";
3
+
4
+ export function handleKeyDown(
5
+ event: KeyboardEvent,
6
+ canvas: Canvas,
7
+ config: Required<MarkupCanvasConfig>,
8
+ isDragEnabled: boolean,
9
+ isDragging: boolean,
10
+ setters: {
11
+ setIsSpacePressed: (value: boolean) => void;
12
+ }
13
+ ): void {
14
+ if (config.requireSpaceForMouseDrag && event.key === " ") {
15
+ setters.setIsSpacePressed(true);
16
+ updateCursor(canvas, config, isDragEnabled, true, isDragging);
17
+ }
18
+ }
@@ -0,0 +1,28 @@
1
+ import { resetDragState } from "@/lib/events/utils/resetDragState.js";
2
+ import { updateCursor } from "@/lib/events/utils/updateCursor.js";
3
+ import type { Canvas, MarkupCanvasConfig } from "@/types/index.js";
4
+
5
+ export function handleKeyUp(
6
+ event: KeyboardEvent,
7
+ canvas: Canvas,
8
+ config: Required<MarkupCanvasConfig>,
9
+ isDragEnabled: boolean,
10
+ isDragging: boolean,
11
+ setters: {
12
+ setIsSpacePressed: (value: boolean) => void;
13
+ setIsDragging: (value: boolean) => void;
14
+ setDragButton: (value: number) => void;
15
+ }
16
+ ): void {
17
+ if (config.requireSpaceForMouseDrag && event.key === " ") {
18
+ setters.setIsSpacePressed(false);
19
+ updateCursor(canvas, config, isDragEnabled, false, isDragging);
20
+ // Stop dragging if currently dragging
21
+ if (isDragging) {
22
+ resetDragState(canvas, config, isDragEnabled, false, {
23
+ setIsDragging: setters.setIsDragging,
24
+ setDragButton: setters.setDragButton,
25
+ });
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,114 @@
1
+ import { getAdaptiveZoomSpeed } from "@/lib/events/utils/getAdaptiveZoomSpeed.js";
2
+ import { withRulerOffsets } from "@/lib/helpers/index.js";
3
+ import { clampZoom } from "@/lib/matrix/clampZoom.js";
4
+ import { getZoomToMouseTransform } from "@/lib/matrix/getZoomToMouseTransform.js";
5
+ import type { Canvas, MarkupCanvasConfig, Transform } from "@/types/index.js";
6
+
7
+ export function setupKeyboardEvents(canvas: Canvas, config: Required<MarkupCanvasConfig>): () => void {
8
+ // Track mouse position
9
+ let lastMouseX = 0;
10
+ let lastMouseY = 0;
11
+
12
+ function handleMouseMove(event: MouseEvent): void {
13
+ const rect = canvas.container.getBoundingClientRect();
14
+ const rawMouseX = event.clientX - rect.left;
15
+ const rawMouseY = event.clientY - rect.top;
16
+
17
+ withRulerOffsets(canvas, rawMouseX, rawMouseY, (adjustedX, adjustedY) => {
18
+ lastMouseX = adjustedX;
19
+ lastMouseY = adjustedY;
20
+ });
21
+ }
22
+
23
+ function handleKeyDown(event: Event): void {
24
+ if (!(event instanceof KeyboardEvent)) return;
25
+
26
+ if (config.limitKeyboardEventsToCanvas && document.activeElement !== canvas.container) return;
27
+
28
+ const isFastPan = event.shiftKey;
29
+ const panDistance = config.keyboardPanStep * (isFastPan ? config.keyboardFastMultiplier : 1);
30
+
31
+ let handled = false;
32
+ const newTransform: Partial<Transform> = {};
33
+
34
+ switch (event.key) {
35
+ case "ArrowLeft":
36
+ newTransform.translateX = canvas.transform.translateX + panDistance;
37
+ handled = true;
38
+ break;
39
+ case "ArrowRight":
40
+ newTransform.translateX = canvas.transform.translateX - panDistance;
41
+ handled = true;
42
+ break;
43
+ case "ArrowUp":
44
+ newTransform.translateY = canvas.transform.translateY + panDistance;
45
+ handled = true;
46
+ break;
47
+ case "ArrowDown":
48
+ newTransform.translateY = canvas.transform.translateY - panDistance;
49
+ handled = true;
50
+ break;
51
+ case "=":
52
+ case "+":
53
+ {
54
+ const adaptiveZoomStep = config.enableAdaptiveSpeed
55
+ ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
56
+ : config.keyboardZoomStep;
57
+ newTransform.scale = clampZoom(canvas.transform.scale * (1 + adaptiveZoomStep), config);
58
+ handled = true;
59
+ }
60
+ break;
61
+ case "-":
62
+ {
63
+ const adaptiveZoomStep = config.enableAdaptiveSpeed
64
+ ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
65
+ : config.keyboardZoomStep;
66
+ newTransform.scale = clampZoom(canvas.transform.scale * (1 - adaptiveZoomStep), config);
67
+ handled = true;
68
+ }
69
+ break;
70
+ case "0":
71
+ if (event.metaKey || event.ctrlKey) {
72
+ const targetScale = 1.0;
73
+ const zoomFactor = targetScale / canvas.transform.scale;
74
+
75
+ const zoomTransform = getZoomToMouseTransform(lastMouseX, lastMouseY, canvas.transform, zoomFactor, config);
76
+
77
+ Object.assign(newTransform, zoomTransform);
78
+ handled = true;
79
+ }
80
+ break;
81
+ case "g":
82
+ case "G":
83
+ if (canvas.toggleGrid) {
84
+ canvas.toggleGrid();
85
+ }
86
+ handled = true;
87
+ break;
88
+ case "r":
89
+ case "R":
90
+ if (!event.metaKey && !event.ctrlKey && !event.altKey && canvas.toggleRulers) {
91
+ canvas.toggleRulers();
92
+ handled = true;
93
+ }
94
+ break;
95
+ }
96
+
97
+ if (handled) {
98
+ event.preventDefault();
99
+ if (Object.keys(newTransform).length > 0) {
100
+ canvas.updateTransform(newTransform);
101
+ }
102
+ }
103
+ }
104
+
105
+ const keyboardTarget = config.limitKeyboardEventsToCanvas ? canvas.container : document;
106
+
107
+ keyboardTarget.addEventListener("keydown", handleKeyDown);
108
+ canvas.container.addEventListener("mousemove", handleMouseMove);
109
+
110
+ return () => {
111
+ keyboardTarget.removeEventListener("keydown", handleKeyDown);
112
+ canvas.container.removeEventListener("mousemove", handleMouseMove);
113
+ };
114
+ }
@@ -0,0 +1,115 @@
1
+ import { getAdaptiveZoomSpeed } from "@/lib/events/utils/getAdaptiveZoomSpeed.js";
2
+ import { withRulerOffsets } from "@/lib/helpers/index.js";
3
+ import { clampZoom } from "@/lib/matrix/clampZoom.js";
4
+ import { getZoomToMouseTransform } from "@/lib/matrix/getZoomToMouseTransform.js";
5
+ import type { Canvas, MarkupCanvasConfig, Transform } from "@/types/index.js";
6
+
7
+ export function setupKeyboardEvents(canvas: Canvas, config: Required<MarkupCanvasConfig>): () => void {
8
+ // Track mouse position
9
+ let lastMouseX = 0;
10
+ let lastMouseY = 0;
11
+
12
+ function handleMouseMove(event: MouseEvent): void {
13
+ const rect = canvas.container.getBoundingClientRect();
14
+ const rawMouseX = event.clientX - rect.left;
15
+ const rawMouseY = event.clientY - rect.top;
16
+
17
+ // Use the new helper to adjust for ruler offset
18
+ withRulerOffsets(canvas, rawMouseX, rawMouseY, (adjustedX, adjustedY) => {
19
+ lastMouseX = adjustedX;
20
+ lastMouseY = adjustedY;
21
+ });
22
+ }
23
+
24
+ function handleKeyDown(event: Event): void {
25
+ if (!(event instanceof KeyboardEvent)) return;
26
+
27
+ if (config.limitKeyboardEventsToCanvas && document.activeElement !== canvas.container) return;
28
+
29
+ const isFastPan = event.shiftKey;
30
+ const panDistance = config.keyboardPanStep * (isFastPan ? config.keyboardFastMultiplier : 1);
31
+
32
+ let handled = false;
33
+ const newTransform: Partial<Transform> = {};
34
+
35
+ switch (event.key) {
36
+ case "ArrowLeft":
37
+ newTransform.translateX = canvas.transform.translateX + panDistance;
38
+ handled = true;
39
+ break;
40
+ case "ArrowRight":
41
+ newTransform.translateX = canvas.transform.translateX - panDistance;
42
+ handled = true;
43
+ break;
44
+ case "ArrowUp":
45
+ newTransform.translateY = canvas.transform.translateY + panDistance;
46
+ handled = true;
47
+ break;
48
+ case "ArrowDown":
49
+ newTransform.translateY = canvas.transform.translateY - panDistance;
50
+ handled = true;
51
+ break;
52
+ case "=":
53
+ case "+":
54
+ {
55
+ const adaptiveZoomStep = config.enableAdaptiveSpeed
56
+ ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
57
+ : config.keyboardZoomStep;
58
+ newTransform.scale = clampZoom(canvas.transform.scale * (1 + adaptiveZoomStep), config);
59
+ handled = true;
60
+ }
61
+ break;
62
+ case "-":
63
+ {
64
+ const adaptiveZoomStep = config.enableAdaptiveSpeed
65
+ ? getAdaptiveZoomSpeed(canvas, config.keyboardZoomStep)
66
+ : config.keyboardZoomStep;
67
+ newTransform.scale = clampZoom(canvas.transform.scale * (1 - adaptiveZoomStep), config);
68
+ handled = true;
69
+ }
70
+ break;
71
+ case "0":
72
+ if (event.metaKey || event.ctrlKey) {
73
+ const targetScale = 1.0;
74
+ const zoomFactor = targetScale / canvas.transform.scale;
75
+
76
+ const zoomTransform = getZoomToMouseTransform(lastMouseX, lastMouseY, canvas.transform, zoomFactor, config);
77
+
78
+ Object.assign(newTransform, zoomTransform);
79
+ handled = true;
80
+ }
81
+ break;
82
+ case "g":
83
+ case "G":
84
+ if (canvas.toggleGrid) {
85
+ canvas.toggleGrid();
86
+ }
87
+ handled = true;
88
+ break;
89
+ case "r":
90
+ case "R":
91
+ if (!event.metaKey && !event.ctrlKey && !event.altKey && canvas.toggleRulers) {
92
+ canvas.toggleRulers();
93
+ handled = true;
94
+ }
95
+ break;
96
+ }
97
+
98
+ if (handled) {
99
+ event.preventDefault();
100
+ if (Object.keys(newTransform).length > 0) {
101
+ canvas.updateTransform(newTransform);
102
+ }
103
+ }
104
+ }
105
+
106
+ const keyboardTarget = config.limitKeyboardEventsToCanvas ? canvas.container : document;
107
+
108
+ keyboardTarget.addEventListener("keydown", handleKeyDown);
109
+ canvas.container.addEventListener("mousemove", handleMouseMove);
110
+
111
+ return () => {
112
+ keyboardTarget.removeEventListener("keydown", handleKeyDown);
113
+ canvas.container.removeEventListener("mousemove", handleMouseMove);
114
+ };
115
+ }