@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.
- package/README.md +245 -0
- package/dist/index.d.ts +2 -0
- package/dist/lib/MarkupCanvas.d.ts +78 -0
- package/dist/lib/canvas/calcVisibleArea.d.ts +10 -0
- package/dist/lib/canvas/checkContainerDimensions.d.ts +1 -0
- package/dist/lib/canvas/config.d.ts +2 -0
- package/dist/lib/canvas/createCanvas.d.ts +2 -0
- package/dist/lib/canvas/createCanvasLayers.d.ts +6 -0
- package/dist/lib/canvas/getCanvasBounds.d.ts +2 -0
- package/dist/lib/canvas/getCanvasMethods.d.ts +12 -0
- package/dist/lib/canvas/getEmptyBounds.d.ts +2 -0
- package/dist/lib/canvas/index.d.ts +3 -0
- package/dist/lib/canvas/moveExistingContent.d.ts +1 -0
- package/dist/lib/canvas/setupCanvasContainer.d.ts +1 -0
- package/dist/lib/canvas/setupContentLayer.d.ts +1 -0
- package/dist/lib/canvas/setupTransformLayer.d.ts +2 -0
- package/dist/lib/config/constants.d.ts +2 -0
- package/dist/lib/config/createMarkupCanvasConfig.d.ts +2 -0
- package/dist/lib/constants.d.ts +7 -0
- package/dist/lib/events/EventEmitter.d.ts +7 -0
- package/dist/lib/events/constants.d.ts +7 -0
- package/dist/lib/events/index.d.ts +6 -0
- package/dist/lib/events/keyboard/handleKeyDown.d.ts +4 -0
- package/dist/lib/events/keyboard/handleKeyUp.d.ts +6 -0
- package/dist/lib/events/keyboard/setupKeyboardEvents.d.ts +2 -0
- package/dist/lib/events/keyboard/setupKeyboardNavigation.d.ts +2 -0
- package/dist/lib/events/mouse/handleClickToZoom.d.ts +2 -0
- package/dist/lib/events/mouse/handleMouseDown.d.ts +11 -0
- package/dist/lib/events/mouse/handleMouseLeave.d.ts +5 -0
- package/dist/lib/events/mouse/handleMouseMove.d.ts +7 -0
- package/dist/lib/events/mouse/handleMouseUp.d.ts +7 -0
- package/dist/lib/events/mouse/setupMouseDrag.d.ts +4 -0
- package/dist/lib/events/mouse/setupMouseEvents.d.ts +4 -0
- package/dist/lib/events/touch/getTouchCenter.d.ts +4 -0
- package/dist/lib/events/touch/getTouchDistance.d.ts +1 -0
- package/dist/lib/events/touch/handleTouchEnd.d.ts +2 -0
- package/dist/lib/events/touch/handleTouchMove.d.ts +2 -0
- package/dist/lib/events/touch/handleTouchStart.d.ts +2 -0
- package/dist/lib/events/touch/setupTouchEvents.d.ts +2 -0
- package/dist/lib/events/trackpad/createTrackpadPanHandler.d.ts +4 -0
- package/dist/lib/events/trackpad/detectTrackpadGesture.d.ts +2 -0
- package/dist/lib/events/utils/getAdaptiveZoomSpeed.d.ts +2 -0
- package/dist/lib/events/utils/resetClickState.d.ts +4 -0
- package/dist/lib/events/utils/resetDragState.d.ts +5 -0
- package/dist/lib/events/utils/updateCursor.d.ts +2 -0
- package/dist/lib/events/wheel/handleWheel.d.ts +2 -0
- package/dist/lib/events/wheel/setupWheelEvents.d.ts +2 -0
- package/dist/lib/events/wheel/setupWheelHandler.d.ts +2 -0
- package/dist/lib/helpers/index.d.ts +6 -0
- package/dist/lib/helpers/withClampedZoom.d.ts +2 -0
- package/dist/lib/helpers/withDebounce.d.ts +1 -0
- package/dist/lib/helpers/withFeatureEnabled.d.ts +2 -0
- package/dist/lib/helpers/withRAF.d.ts +4 -0
- package/dist/lib/helpers/withRulerCheck.d.ts +18 -0
- package/dist/lib/helpers/withRulerOffset.d.ts +3 -0
- package/dist/lib/matrix/canvasToContent.d.ts +2 -0
- package/dist/lib/matrix/clampZoom.d.ts +2 -0
- package/dist/lib/matrix/contentToCanvas.d.ts +2 -0
- package/dist/lib/matrix/createMatrix.d.ts +1 -0
- package/dist/lib/matrix/createMatrixString.d.ts +1 -0
- package/dist/lib/matrix/getZoomToMouseTransform.d.ts +2 -0
- package/dist/lib/matrix/index.d.ts +5 -0
- package/dist/lib/rulers/RulerElements.d.ts +6 -0
- package/dist/lib/rulers/constants.d.ts +19 -0
- package/dist/lib/rulers/createCornerBox.d.ts +2 -0
- package/dist/lib/rulers/createGridOverlay.d.ts +2 -0
- package/dist/lib/rulers/createHorizontalRuler.d.ts +2 -0
- package/dist/lib/rulers/createRulerElements.d.ts +3 -0
- package/dist/lib/rulers/createRulers.d.ts +2 -0
- package/dist/lib/rulers/createVerticalRuler.d.ts +2 -0
- package/dist/lib/rulers/index.d.ts +2 -0
- package/dist/lib/rulers/setupRulerEvents.d.ts +2 -0
- package/dist/lib/rulers/ticks/calculateTickSpacing.d.ts +1 -0
- package/dist/lib/rulers/ticks/createHorizontalTick.d.ts +2 -0
- package/dist/lib/rulers/ticks/createVerticalTick.d.ts +2 -0
- package/dist/lib/rulers/ticks/index.d.ts +3 -0
- package/dist/lib/rulers/updateGrid.d.ts +1 -0
- package/dist/lib/rulers/updateHorizontalRuler.d.ts +2 -0
- package/dist/lib/rulers/updateRulers.d.ts +2 -0
- package/dist/lib/rulers/updateVerticalRuler.d.ts +2 -0
- package/dist/lib/transform/applyTransform.d.ts +1 -0
- package/dist/lib/transform/applyZoomToCanvas.d.ts +2 -0
- package/dist/lib/transform/hardware-acceleration.d.ts +1 -0
- package/dist/lib/transform/index.d.ts +2 -0
- package/dist/lib/transition/disableTransition.d.ts +7 -0
- package/dist/lib/transition/enableTransition.d.ts +7 -0
- package/dist/lib/transition/index.d.ts +3 -0
- package/dist/lib/transition/withTransition.d.ts +2 -0
- package/dist/markup-canvas.cjs.js +2000 -0
- package/dist/markup-canvas.esm.js +1995 -0
- package/dist/markup-canvas.umd.js +2003 -0
- package/dist/markup-canvas.umd.min.js +1 -0
- package/dist/types/canvas.d.ts +86 -0
- package/dist/types/config.d.ts +38 -0
- package/dist/types/events.d.ts +33 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/matrix.d.ts +17 -0
- package/dist/types/rulers.d.ts +31 -0
- package/dist/umd.d.ts +1 -0
- package/package.json +56 -0
- package/src/index.ts +19 -0
- package/src/lib/MarkupCanvas.ts +434 -0
- package/src/lib/canvas/calcVisibleArea.ts +20 -0
- package/src/lib/canvas/checkContainerDimensions.ts +20 -0
- package/src/lib/canvas/config.ts +29 -0
- package/src/lib/canvas/createCanvas.ts +61 -0
- package/src/lib/canvas/createCanvasLayers.ts +39 -0
- package/src/lib/canvas/getCanvasBounds.ts +68 -0
- package/src/lib/canvas/getCanvasMethods.ts +104 -0
- package/src/lib/canvas/getEmptyBounds.ts +22 -0
- package/src/lib/canvas/index.ts +3 -0
- package/src/lib/canvas/moveExistingContent.ts +9 -0
- package/src/lib/canvas/setupCanvasContainer.ts +22 -0
- package/src/lib/canvas/setupContentLayer.ts +6 -0
- package/src/lib/canvas/setupTransformLayer.ts +15 -0
- package/src/lib/config/constants.ts +56 -0
- package/src/lib/config/createMarkupCanvasConfig.ts +56 -0
- package/src/lib/constants.ts +16 -0
- package/src/lib/events/EventEmitter.ts +34 -0
- package/src/lib/events/constants.ts +9 -0
- package/src/lib/events/index.ts +6 -0
- package/src/lib/events/keyboard/handleKeyDown.ts +18 -0
- package/src/lib/events/keyboard/handleKeyUp.ts +28 -0
- package/src/lib/events/keyboard/setupKeyboardEvents.ts +114 -0
- package/src/lib/events/keyboard/setupKeyboardNavigation.ts +115 -0
- package/src/lib/events/mouse/handleClickToZoom.ts +54 -0
- package/src/lib/events/mouse/handleMouseDown.ts +45 -0
- package/src/lib/events/mouse/handleMouseLeave.ts +18 -0
- package/src/lib/events/mouse/handleMouseMove.ts +57 -0
- package/src/lib/events/mouse/handleMouseUp.ts +40 -0
- package/src/lib/events/mouse/setupMouseDrag.ts +159 -0
- package/src/lib/events/mouse/setupMouseEvents.ts +158 -0
- package/src/lib/events/touch/getTouchCenter.ts +6 -0
- package/src/lib/events/touch/getTouchDistance.ts +5 -0
- package/src/lib/events/touch/handleTouchEnd.ts +9 -0
- package/src/lib/events/touch/handleTouchMove.ts +58 -0
- package/src/lib/events/touch/handleTouchStart.ts +14 -0
- package/src/lib/events/touch/setupTouchEvents.ts +40 -0
- package/src/lib/events/trackpad/createTrackpadPanHandler.ts +35 -0
- package/src/lib/events/trackpad/detectTrackpadGesture.ts +22 -0
- package/src/lib/events/utils/getAdaptiveZoomSpeed.ts +21 -0
- package/src/lib/events/utils/resetClickState.ts +4 -0
- package/src/lib/events/utils/resetDragState.ts +17 -0
- package/src/lib/events/utils/updateCursor.ts +20 -0
- package/src/lib/events/wheel/handleWheel.ts +67 -0
- package/src/lib/events/wheel/setupWheelEvents.ts +24 -0
- package/src/lib/events/wheel/setupWheelHandler.ts +24 -0
- package/src/lib/helpers/index.ts +12 -0
- package/src/lib/helpers/withClampedZoom.ts +7 -0
- package/src/lib/helpers/withDebounce.ts +15 -0
- package/src/lib/helpers/withFeatureEnabled.ts +8 -0
- package/src/lib/helpers/withRAF.ts +38 -0
- package/src/lib/helpers/withRulerCheck.ts +52 -0
- package/src/lib/helpers/withRulerOffset.ts +14 -0
- package/src/lib/matrix/canvasToContent.ts +20 -0
- package/src/lib/matrix/clampZoom.ts +5 -0
- package/src/lib/matrix/contentToCanvas.ts +20 -0
- package/src/lib/matrix/createMatrix.ts +3 -0
- package/src/lib/matrix/createMatrixString.ts +3 -0
- package/src/lib/matrix/getZoomToMouseTransform.ts +46 -0
- package/src/lib/matrix/index.ts +5 -0
- package/src/lib/rulers/RulerElements.ts +6 -0
- package/src/lib/rulers/constants.ts +23 -0
- package/src/lib/rulers/createCornerBox.ts +27 -0
- package/src/lib/rulers/createGridOverlay.ts +22 -0
- package/src/lib/rulers/createHorizontalRuler.ts +24 -0
- package/src/lib/rulers/createRulerElements.ts +27 -0
- package/src/lib/rulers/createRulers.ts +94 -0
- package/src/lib/rulers/createVerticalRuler.ts +24 -0
- package/src/lib/rulers/index.ts +2 -0
- package/src/lib/rulers/setupRulerEvents.ts +23 -0
- package/src/lib/rulers/ticks/calculateTickSpacing.ts +15 -0
- package/src/lib/rulers/ticks/createHorizontalTick.ts +41 -0
- package/src/lib/rulers/ticks/createVerticalTick.ts +43 -0
- package/src/lib/rulers/ticks/index.ts +3 -0
- package/src/lib/rulers/updateGrid.ts +11 -0
- package/src/lib/rulers/updateHorizontalRuler.ts +32 -0
- package/src/lib/rulers/updateRulers.ts +33 -0
- package/src/lib/rulers/updateVerticalRuler.ts +31 -0
- package/src/lib/transform/applyTransform.ts +15 -0
- package/src/lib/transform/applyZoomToCanvas.ts +7 -0
- package/src/lib/transform/hardware-acceleration.ts +11 -0
- package/src/lib/transform/index.ts +2 -0
- package/src/lib/transition/disableTransition.ts +33 -0
- package/src/lib/transition/enableTransition.ts +26 -0
- package/src/lib/transition/index.ts +3 -0
- package/src/lib/transition/withTransition.ts +13 -0
- package/src/types/canvas.ts +89 -0
- package/src/types/config.ts +54 -0
- package/src/types/events.ts +31 -0
- package/src/types/index.ts +28 -0
- package/src/types/matrix.ts +19 -0
- package/src/types/rulers.ts +35 -0
- 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,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,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,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
|
+
}
|