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