@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,35 @@
1
+ import { withRAFThrottle } from "@/lib/helpers/index.js";
2
+ import { disableTransition } from "@/lib/transition";
3
+ import type { BaseCanvas, Transform } from "@/types";
4
+
5
+ export const createTrackpadPanHandler = (canvas: BaseCanvas) =>
6
+ withRAFThrottle((...args: unknown[]) => {
7
+ const event = args[0] as WheelEvent;
8
+ if (!event || !canvas?.updateTransform) {
9
+ return false;
10
+ }
11
+
12
+ try {
13
+ const currentTransform = canvas.transform;
14
+
15
+ // Calculate pan delta based on trackpad scroll
16
+ const panSensitivity = 1.0;
17
+ const deltaX = event.deltaX * panSensitivity;
18
+ const deltaY = event.deltaY * panSensitivity;
19
+
20
+ // Apply pan by adjusting translation
21
+ const newTransform: Partial<Transform> = {
22
+ scale: currentTransform.scale,
23
+ translateX: currentTransform.translateX - deltaX,
24
+ translateY: currentTransform.translateY - deltaY,
25
+ };
26
+
27
+ disableTransition(canvas.transformLayer, canvas.config);
28
+
29
+ // Apply the new transform
30
+ return canvas.updateTransform(newTransform);
31
+ } catch (error) {
32
+ console.error("Error handling trackpad pan:", error);
33
+ return false;
34
+ }
35
+ });
@@ -0,0 +1,22 @@
1
+ import type { GestureInfo } from "@/types/index.js";
2
+
3
+ export function detectTrackpadGesture(event: WheelEvent): GestureInfo {
4
+ const isZoomIntent = event.ctrlKey || event.metaKey;
5
+
6
+ const isPixelMode = event.deltaMode === 0;
7
+ const hasSmallDelta = Math.abs(event.deltaY) < 50;
8
+ const hasFractionalDelta = event.deltaY % 1 !== 0;
9
+ const hasMultiAxis = Math.abs(event.deltaX) > 0 && Math.abs(event.deltaY) > 0;
10
+
11
+ const trackpadCriteria = [isPixelMode, hasSmallDelta, hasFractionalDelta, hasMultiAxis];
12
+ const trackpadMatches = trackpadCriteria.filter(Boolean).length;
13
+ const isTrackpad = trackpadMatches >= 2;
14
+
15
+ return {
16
+ isTrackpad,
17
+ isMouseWheel: !isTrackpad,
18
+ isTrackpadScroll: isTrackpad && !isZoomIntent,
19
+ isTrackpadPinch: isTrackpad && isZoomIntent,
20
+ isZoomGesture: isZoomIntent,
21
+ };
22
+ }
@@ -0,0 +1,21 @@
1
+ import { ADAPTIVE_ZOOM_FACTOR, REFERENCE_DISPLAY_AREA } from "@/lib/events/constants.js";
2
+ import type { BaseCanvas } from "@/types/index.js";
3
+
4
+ export function getAdaptiveZoomSpeed(canvas: BaseCanvas, baseSpeed: number): number {
5
+ if (!canvas?.getBounds) {
6
+ return baseSpeed;
7
+ }
8
+
9
+ try {
10
+ const bounds = canvas.getBounds();
11
+ const displayArea = bounds.width * bounds.height;
12
+
13
+ const rawScaleFactor = (displayArea / REFERENCE_DISPLAY_AREA) ** ADAPTIVE_ZOOM_FACTOR;
14
+ const adaptiveSpeed = baseSpeed * rawScaleFactor;
15
+
16
+ return adaptiveSpeed;
17
+ } catch (error) {
18
+ console.warn("Failed to calculate adaptive zoom speed, using base speed:", error);
19
+ return baseSpeed;
20
+ }
21
+ }
@@ -0,0 +1,4 @@
1
+ export function resetClickState(setters: { setMouseDownTime: (value: number) => void; setHasDragged: (value: boolean) => void }): void {
2
+ setters.setMouseDownTime(0);
3
+ setters.setHasDragged(false);
4
+ }
@@ -0,0 +1,17 @@
1
+ import { updateCursor } from "@/lib/events/utils/updateCursor.js";
2
+ import type { Canvas, MarkupCanvasConfig } from "@/types/index.js";
3
+
4
+ export function resetDragState(
5
+ canvas: Canvas,
6
+ config: Required<MarkupCanvasConfig>,
7
+ isDragEnabled: boolean,
8
+ isSpacePressed: boolean,
9
+ setters: {
10
+ setIsDragging: (value: boolean) => void;
11
+ setDragButton: (value: number) => void;
12
+ }
13
+ ): void {
14
+ setters.setIsDragging(false);
15
+ setters.setDragButton(-1);
16
+ updateCursor(canvas, config, isDragEnabled, isSpacePressed, false);
17
+ }
@@ -0,0 +1,20 @@
1
+ import type { Canvas, MarkupCanvasConfig } from "@/types/index.js";
2
+
3
+ export function updateCursor(
4
+ canvas: Canvas,
5
+ config: Required<MarkupCanvasConfig>,
6
+ isDragEnabled: boolean,
7
+ isSpacePressed: boolean,
8
+ isDragging: boolean
9
+ ): void {
10
+ if (!isDragEnabled) {
11
+ canvas.container.style.cursor = "default";
12
+ return;
13
+ }
14
+
15
+ if (config.requireSpaceForMouseDrag) {
16
+ canvas.container.style.cursor = isSpacePressed ? "grab" : "default";
17
+ } else {
18
+ canvas.container.style.cursor = isDragging ? "grabbing" : "grab";
19
+ }
20
+ }
@@ -0,0 +1,67 @@
1
+ import { TRACKPAD_PINCH_SPEED_FACTOR } from "@/lib/events/constants.js";
2
+ import { detectTrackpadGesture } from "@/lib/events/trackpad/detectTrackpadGesture.js";
3
+ import { getAdaptiveZoomSpeed } from "@/lib/events/utils/getAdaptiveZoomSpeed.js";
4
+ import { withRulerOffset } from "@/lib/helpers/index.js";
5
+ import { applyZoomToCanvas } from "@/lib/transform/applyZoomToCanvas.js";
6
+ import type { Canvas, MarkupCanvasConfig } from "@/types/index.js";
7
+
8
+ export function handleWheel(event: WheelEvent, canvas: Canvas, config: Required<MarkupCanvasConfig>): boolean {
9
+ if (!event || typeof event.deltaY !== "number") {
10
+ console.warn("Invalid wheel event provided");
11
+ return false;
12
+ }
13
+
14
+ if (!canvas?.updateTransform) {
15
+ console.warn("Invalid canvas provided to handleWheelEvent");
16
+ return false;
17
+ }
18
+
19
+ try {
20
+ event.preventDefault();
21
+
22
+ // Get mouse position
23
+ const rect = canvas.container.getBoundingClientRect();
24
+ const rawMouseX = event.clientX - rect.left;
25
+ const rawMouseY = event.clientY - rect.top;
26
+
27
+ // Account for ruler offset
28
+ const { mouseX, mouseY } = withRulerOffset(canvas, rawMouseX, rawMouseY, (adjustedX, adjustedY) => ({
29
+ mouseX: adjustedX,
30
+ mouseY: adjustedY,
31
+ }));
32
+
33
+ // Use the standard zoom speed
34
+ const baseZoomSpeed = config.zoomSpeed;
35
+
36
+ // Simple gesture detection
37
+ const gestureInfo = detectTrackpadGesture(event);
38
+
39
+ if (!gestureInfo.isZoomGesture) {
40
+ // Not a zoom gesture, ignore
41
+ return false;
42
+ }
43
+
44
+ // Apply display-size adaptive scaling if enabled
45
+ const currentZoomSpeed = config.enableAdaptiveSpeed ? getAdaptiveZoomSpeed(canvas, baseZoomSpeed) : baseZoomSpeed;
46
+
47
+ // Simple device-based zoom speed adjustment
48
+ let deviceZoomSpeed = currentZoomSpeed;
49
+
50
+ if (gestureInfo.isTrackpadPinch) {
51
+ const baseTrackpadSpeed = config.zoomSpeed * TRACKPAD_PINCH_SPEED_FACTOR;
52
+
53
+ deviceZoomSpeed = config.enableAdaptiveSpeed ? getAdaptiveZoomSpeed(canvas, baseTrackpadSpeed) : baseTrackpadSpeed;
54
+ }
55
+
56
+ // Calculate zoom delta
57
+ const zoomDirection = event.deltaY < 0 ? 1 : -1;
58
+
59
+ // Use exponential zoom for more natural feel
60
+ const rawZoomMultiplier = zoomDirection > 0 ? 1 + deviceZoomSpeed : 1 / (1 + deviceZoomSpeed);
61
+
62
+ return applyZoomToCanvas(canvas, rawZoomMultiplier, mouseX, mouseY);
63
+ } catch (error) {
64
+ console.error("Error handling wheel event:", error);
65
+ return false;
66
+ }
67
+ }
@@ -0,0 +1,24 @@
1
+ import { createTrackpadPanHandler } from "@/lib/events/trackpad/createTrackpadPanHandler";
2
+ import { detectTrackpadGesture } from "@/lib/events/trackpad/detectTrackpadGesture";
3
+ import { handleWheel } from "@/lib/events/wheel/handleWheel";
4
+ import type { Canvas, MarkupCanvasConfig } from "@/types";
5
+
6
+ export function setupWheelEvents(canvas: Canvas, config: Required<MarkupCanvasConfig>): () => void {
7
+ const trackpadPanHandler = createTrackpadPanHandler(canvas);
8
+
9
+ const wheelHandler = (event: WheelEvent) => {
10
+ const gestureInfo = detectTrackpadGesture(event);
11
+
12
+ if (gestureInfo.isTrackpadScroll) {
13
+ return trackpadPanHandler(event);
14
+ }
15
+
16
+ return handleWheel(event, canvas, config);
17
+ };
18
+
19
+ canvas.container.addEventListener("wheel", wheelHandler, { passive: false });
20
+
21
+ return () => {
22
+ canvas.container.removeEventListener("wheel", wheelHandler);
23
+ };
24
+ }
@@ -0,0 +1,24 @@
1
+ import { createTrackpadPanHandler } from "@/lib/events/trackpad/createTrackpadPanHandler";
2
+ import { detectTrackpadGesture } from "@/lib/events/trackpad/detectTrackpadGesture";
3
+ import { handleWheel } from "@/lib/events/wheel/handleWheel";
4
+ import type { Canvas, MarkupCanvasConfig } from "@/types";
5
+
6
+ export function setupWheelEvents(canvas: Canvas, config: Required<MarkupCanvasConfig>): () => void {
7
+ const trackpadPanHandler = createTrackpadPanHandler(canvas);
8
+
9
+ const wheelHandler = (event: WheelEvent) => {
10
+ const gestureInfo = detectTrackpadGesture(event);
11
+
12
+ if (gestureInfo.isTrackpadScroll) {
13
+ return trackpadPanHandler(event);
14
+ }
15
+
16
+ return handleWheel(event, canvas, config);
17
+ };
18
+
19
+ canvas.container.addEventListener("wheel", wheelHandler, { passive: false });
20
+
21
+ return () => {
22
+ canvas.container.removeEventListener("wheel", wheelHandler);
23
+ };
24
+ }
@@ -0,0 +1,12 @@
1
+ export { withClampedZoom } from "./withClampedZoom.js";
2
+ export { withDebounce } from "./withDebounce.js";
3
+ export { withFeatureEnabled } from "./withFeatureEnabled.js";
4
+ export { withRAF, withRAFThrottle } from "./withRAF.js";
5
+ export {
6
+ withRulerAdjustment,
7
+ withRulerCheck,
8
+ withRulerOffsetObject,
9
+ withRulerOffsets,
10
+ withRulerSize,
11
+ } from "./withRulerCheck.js";
12
+ export { withRulerOffset } from "./withRulerOffset.js";
@@ -0,0 +1,7 @@
1
+ import { clampZoom } from "@/lib/matrix/clampZoom.js";
2
+ import type { MarkupCanvasConfig } from "@/types";
3
+
4
+ export function withClampedZoom<T>(config: Required<MarkupCanvasConfig>, operation: (clamp: (scale: number) => number) => T): T {
5
+ const clampFunction = (scale: number) => clampZoom(scale, config);
6
+ return operation(clampFunction);
7
+ }
@@ -0,0 +1,15 @@
1
+ const debounceTimers = new Map<string, number>();
2
+
3
+ export function withDebounce(key: string, delay: number, operation: () => void): void {
4
+ const existingTimer = debounceTimers.get(key);
5
+ if (existingTimer) {
6
+ clearTimeout(existingTimer);
7
+ }
8
+
9
+ const timer = window.setTimeout(() => {
10
+ operation();
11
+ debounceTimers.delete(key);
12
+ }, delay);
13
+
14
+ debounceTimers.set(key, timer);
15
+ }
@@ -0,0 +1,8 @@
1
+ import type { MarkupCanvasConfig } from "@/types";
2
+
3
+ export function withFeatureEnabled<T>(config: MarkupCanvasConfig, feature: keyof MarkupCanvasConfig, operation: () => T): T | null {
4
+ if (config[feature]) {
5
+ return operation();
6
+ }
7
+ return null;
8
+ }
@@ -0,0 +1,38 @@
1
+ export function withRAF(operation: () => void): () => void {
2
+ const rafId = requestAnimationFrame(() => {
3
+ operation();
4
+ });
5
+
6
+ return () => {
7
+ cancelAnimationFrame(rafId);
8
+ };
9
+ }
10
+
11
+ export function withRAFThrottle<T extends (...args: unknown[]) => void>(func: T): T & { cleanup: () => void } {
12
+ let rafId: number | null = null;
13
+ let lastArgs: Parameters<T> | null = null;
14
+
15
+ const throttled = (...args: Parameters<T>) => {
16
+ lastArgs = args;
17
+
18
+ if (rafId === null) {
19
+ rafId = requestAnimationFrame(() => {
20
+ if (lastArgs) {
21
+ func(...lastArgs);
22
+ }
23
+ rafId = null;
24
+ lastArgs = null;
25
+ });
26
+ }
27
+ };
28
+
29
+ throttled.cleanup = () => {
30
+ if (rafId !== null) {
31
+ cancelAnimationFrame(rafId);
32
+ rafId = null;
33
+ lastArgs = null;
34
+ }
35
+ };
36
+
37
+ return throttled as T & { cleanup: () => void };
38
+ }
@@ -0,0 +1,52 @@
1
+ import { RULER_SIZE } from "../rulers/constants";
2
+
3
+ export function withRulerCheck<T>(canvas: { container: HTMLElement }, operation: (hasRulers: boolean) => T): T {
4
+ const hasRulers = canvas.container.querySelector(".canvas-ruler") !== null;
5
+ return operation(hasRulers);
6
+ }
7
+
8
+ export function withRulerSize<T>(canvas: { container: HTMLElement }, operation: (rulerSize: number) => T): T {
9
+ const hasRulers = canvas.container.querySelector(".canvas-ruler") !== null;
10
+ const rulerSize = hasRulers ? RULER_SIZE : 0;
11
+ return operation(rulerSize);
12
+ }
13
+
14
+ export function withRulerAdjustment(
15
+ canvas: { container: HTMLElement },
16
+ value: number,
17
+ operation?: (adjustedValue: number) => void
18
+ ): number {
19
+ return withRulerSize(canvas, (rulerSize) => {
20
+ const adjusted = value - rulerSize;
21
+ operation?.(adjusted);
22
+ return adjusted;
23
+ });
24
+ }
25
+
26
+ export function withRulerOffsets<T>(
27
+ canvas: { container: HTMLElement },
28
+ x: number,
29
+ y: number,
30
+ operation: (adjustedX: number, adjustedY: number) => T
31
+ ): T {
32
+ return withRulerSize(canvas, (rulerSize) => {
33
+ const adjustedX = x - rulerSize;
34
+ const adjustedY = y - rulerSize;
35
+ return operation(adjustedX, adjustedY);
36
+ });
37
+ }
38
+
39
+ export function withRulerOffsetObject<T, C extends { x: number; y: number }>(
40
+ canvas: { container: HTMLElement },
41
+ coords: C,
42
+ operation: (adjusted: C) => T
43
+ ): T {
44
+ return withRulerSize(canvas, (rulerSize) => {
45
+ const adjusted = {
46
+ ...coords,
47
+ x: coords.x - rulerSize,
48
+ y: coords.y - rulerSize,
49
+ } as C;
50
+ return operation(adjusted);
51
+ });
52
+ }
@@ -0,0 +1,14 @@
1
+ import { RULER_SIZE } from "../rulers/constants";
2
+
3
+ export function withRulerOffset<T>(
4
+ canvas: { container: HTMLElement },
5
+ x: number,
6
+ y: number,
7
+ operation: (adjustedX: number, adjustedY: number) => T
8
+ ): T {
9
+ const hasRulers = canvas.container.querySelector(".canvas-ruler") !== null;
10
+ const adjustedX = hasRulers ? x - RULER_SIZE : x;
11
+ const adjustedY = hasRulers ? y - RULER_SIZE : y;
12
+
13
+ return operation(adjustedX, adjustedY);
14
+ }
@@ -0,0 +1,20 @@
1
+ import type { Point } from "@/types";
2
+
3
+ export function canvasToContent(canvasX: number, canvasY: number, matrix: DOMMatrix): Point {
4
+ if (!matrix?.inverse) {
5
+ return { x: canvasX, y: canvasY };
6
+ }
7
+
8
+ try {
9
+ const inverseMatrix = matrix.inverse();
10
+ const point = new DOMPoint(canvasX, canvasY);
11
+ const transformed = point.matrixTransform(inverseMatrix);
12
+ return {
13
+ x: transformed.x,
14
+ y: transformed.y,
15
+ };
16
+ } catch (error) {
17
+ console.warn("Canvas to content conversion failed:", error);
18
+ return { x: canvasX, y: canvasY };
19
+ }
20
+ }
@@ -0,0 +1,5 @@
1
+ import type { MarkupCanvasConfig } from "@/types/index.js";
2
+
3
+ export function clampZoom(scale: number, config: Required<MarkupCanvasConfig>): number {
4
+ return Math.max(config.minZoom, Math.min(config.maxZoom, scale));
5
+ }
@@ -0,0 +1,20 @@
1
+ import type { Point } from "@/types";
2
+
3
+ export function contentToCanvas(contentX: number, contentY: number, matrix: DOMMatrix): Point {
4
+ if (!matrix?.transformPoint) {
5
+ return { x: contentX, y: contentY };
6
+ }
7
+
8
+ try {
9
+ const point = new DOMPoint(contentX, contentY);
10
+ const transformed = point.matrixTransform(matrix);
11
+
12
+ return {
13
+ x: transformed.x,
14
+ y: transformed.y,
15
+ };
16
+ } catch (error) {
17
+ console.warn("Content to canvas conversion failed:", error);
18
+ return { x: contentX, y: contentY };
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ export function createMatrix(scale: number, translateX: number, translateY: number): DOMMatrix {
2
+ return new DOMMatrix([scale, 0, 0, scale, translateX, translateY]);
3
+ }
@@ -0,0 +1,3 @@
1
+ export function createMatrixString(matrix: DOMMatrix): string {
2
+ 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})`;
3
+ }
@@ -0,0 +1,46 @@
1
+ import { DEFAULT_ZOOM, ZOOM_CHANGE_THRESHOLD } from "@/lib/constants.js";
2
+ import { clampZoom } from "@/lib/matrix/clampZoom.js";
3
+ import type { MarkupCanvasConfig, Transform } from "@/types/index.js";
4
+ import { RULER_SIZE } from "../rulers/constants";
5
+
6
+ export function getZoomToMouseTransform(
7
+ mouseX: number,
8
+ mouseY: number,
9
+ currentTransform: Transform,
10
+ zoomFactor: number,
11
+ config: Required<MarkupCanvasConfig>
12
+ ): Transform {
13
+ const rulerOffset = config.enableRulers ? -RULER_SIZE : 0;
14
+
15
+ const transform = currentTransform || {
16
+ scale: DEFAULT_ZOOM,
17
+ translateX: rulerOffset,
18
+ translateY: rulerOffset,
19
+ };
20
+
21
+ const { scale, translateX, translateY } = transform;
22
+
23
+ // Calculate new scale with clamping
24
+ const newScale = clampZoom(scale * zoomFactor, config);
25
+
26
+ // Early return if zoom didn't change (hit bounds)
27
+ if (Math.abs(newScale - scale) < ZOOM_CHANGE_THRESHOLD) {
28
+ return { scale, translateX, translateY };
29
+ }
30
+
31
+ // Convert mouse position to content space
32
+ // Formula: contentPos = (mousePos - translate) / scale
33
+ const contentX = (mouseX - translateX) / scale;
34
+ const contentY = (mouseY - translateY) / scale;
35
+
36
+ // Calculate new translation
37
+ // Formula: newTranslate = mousePos - (contentPos * newScale)
38
+ const newTranslateX = mouseX - contentX * newScale;
39
+ const newTranslateY = mouseY - contentY * newScale;
40
+
41
+ return {
42
+ scale: newScale,
43
+ translateX: newTranslateX,
44
+ translateY: newTranslateY,
45
+ };
46
+ }
@@ -0,0 +1,5 @@
1
+ export { canvasToContent } from "./canvasToContent.js";
2
+ export { clampZoom } from "./clampZoom.js";
3
+ export { contentToCanvas } from "./contentToCanvas.js";
4
+ export { createMatrix } from "./createMatrix.js";
5
+ export { getZoomToMouseTransform } from "./getZoomToMouseTransform.js";
@@ -0,0 +1,6 @@
1
+ export interface RulerElements {
2
+ horizontalRuler: HTMLElement;
3
+ verticalRuler: HTMLElement;
4
+ cornerBox: HTMLElement;
5
+ gridOverlay?: HTMLElement;
6
+ }
@@ -0,0 +1,23 @@
1
+ // Rulers
2
+ export const RULER_SIZE = 24;
3
+
4
+ export const RULER_Z_INDEX = {
5
+ GRID: 100,
6
+ RULERS: 1000,
7
+ CORNER: 1001,
8
+ } as const;
9
+
10
+ export const TICK_SETTINGS = {
11
+ MAJOR_HEIGHT: 6,
12
+ MINOR_HEIGHT: 4,
13
+ MAJOR_WIDTH: 8,
14
+ MINOR_WIDTH: 4,
15
+ MAJOR_MULTIPLIER: 5,
16
+ LABEL_INTERVAL: 100,
17
+ } as const;
18
+
19
+ export const GRID_SETTINGS = {
20
+ BASE_SIZE: 100,
21
+ MIN_SIZE: 20,
22
+ MAX_SIZE: 200,
23
+ } as const;
@@ -0,0 +1,27 @@
1
+ import type { RulerOptions } from "@/types/index.js";
2
+ import { RULER_SIZE, RULER_Z_INDEX } from "./constants";
3
+
4
+ export function createCornerBox(config: Required<RulerOptions>): HTMLElement {
5
+ const corner = document.createElement("div");
6
+ corner.className = "canvas-ruler corner-box";
7
+ corner.style.cssText = `
8
+ position: absolute;
9
+ top: 0;
10
+ left: 0;
11
+ width: ${RULER_SIZE}px;
12
+ height: ${RULER_SIZE}px;
13
+ background: ${config.backgroundColor};
14
+ border-right: 1px solid ${config.borderColor};
15
+ border-bottom: 1px solid ${config.borderColor};
16
+ z-index: ${RULER_Z_INDEX.CORNER};
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ font-family: ${config.fontFamily};
21
+ font-size: ${config.fontSize - 2}px;
22
+ color: ${config.textColor};
23
+ pointer-events: none;
24
+ `;
25
+ corner.textContent = config.units;
26
+ return corner;
27
+ }
@@ -0,0 +1,22 @@
1
+ import type { RulerOptions } from "@/types/index.js";
2
+ import { RULER_SIZE, RULER_Z_INDEX } from "./constants";
3
+
4
+ export function createGridOverlay(config: Required<RulerOptions>): HTMLElement {
5
+ const grid = document.createElement("div");
6
+ grid.className = "canvas-ruler grid-overlay";
7
+ grid.style.cssText = `
8
+ position: absolute;
9
+ top: ${RULER_SIZE}px;
10
+ left: ${RULER_SIZE}px;
11
+ right: 0;
12
+ bottom: 0;
13
+ pointer-events: none;
14
+ z-index: ${RULER_Z_INDEX.GRID};
15
+ background-image:
16
+ linear-gradient(${config.gridColor} 1px, transparent 1px),
17
+ linear-gradient(90deg, ${config.gridColor} 1px, transparent 1px);
18
+ background-size: 100px 100px;
19
+ opacity: 0.5;
20
+ `;
21
+ return grid;
22
+ }
@@ -0,0 +1,24 @@
1
+ import type { RulerOptions } from "@/types/index.js";
2
+ import { RULER_SIZE, RULER_Z_INDEX } from "./constants";
3
+
4
+ export function createHorizontalRuler(config: Required<RulerOptions>): HTMLElement {
5
+ const ruler = document.createElement("div");
6
+ ruler.className = "canvas-ruler horizontal-ruler";
7
+ ruler.style.cssText = `
8
+ position: absolute;
9
+ top: 0;
10
+ left: ${RULER_SIZE}px;
11
+ right: 0;
12
+ height: ${RULER_SIZE}px;
13
+ background: ${config.backgroundColor};
14
+ border-bottom: 1px solid ${config.borderColor};
15
+ border-right: 1px solid ${config.borderColor};
16
+ z-index: ${RULER_Z_INDEX.RULERS};
17
+ pointer-events: none;
18
+ font-family: ${config.fontFamily};
19
+ font-size: ${config.fontSize}px;
20
+ color: ${config.textColor};
21
+ overflow: hidden;
22
+ `;
23
+ return ruler;
24
+ }
@@ -0,0 +1,27 @@
1
+ import type { RulerOptions } from "@/types/index.js";
2
+ import { createCornerBox } from "./createCornerBox.js";
3
+ import { createGridOverlay } from "./createGridOverlay.js";
4
+ import { createHorizontalRuler } from "./createHorizontalRuler.js";
5
+ import { createVerticalRuler } from "./createVerticalRuler.js";
6
+ import type { RulerElements } from "./RulerElements.js";
7
+
8
+ export function createRulerElements(container: HTMLElement, config: Required<RulerOptions>): RulerElements {
9
+ const horizontalRuler = createHorizontalRuler(config);
10
+ const verticalRuler = createVerticalRuler(config);
11
+ const cornerBox = createCornerBox(config);
12
+ const gridOverlay = config.showGrid ? createGridOverlay(config) : undefined;
13
+
14
+ container.appendChild(horizontalRuler);
15
+ container.appendChild(verticalRuler);
16
+ container.appendChild(cornerBox);
17
+ if (gridOverlay) {
18
+ container.appendChild(gridOverlay);
19
+ }
20
+
21
+ return {
22
+ horizontalRuler,
23
+ verticalRuler,
24
+ cornerBox,
25
+ gridOverlay,
26
+ };
27
+ }