@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,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,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,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,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 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,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
|
+
}
|