tokimeki-image-editor 0.2.0 → 0.2.1
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.
|
@@ -14,6 +14,10 @@ const colorPresets = ['#FF6B6B', '#FFA94D', '#FFD93D', '#6BCB77', '#4D96FF', '#9
|
|
|
14
14
|
// Drawing state
|
|
15
15
|
let isDrawing = $state(false);
|
|
16
16
|
let currentAnnotation = $state(null);
|
|
17
|
+
// Panning state (Space + drag)
|
|
18
|
+
let isSpaceHeld = $state(false);
|
|
19
|
+
let isPanning = $state(false);
|
|
20
|
+
let panStart = $state(null);
|
|
17
21
|
// Helper to get coordinates from mouse or touch event
|
|
18
22
|
function getEventCoords(event) {
|
|
19
23
|
if ('touches' in event && event.touches.length > 0) {
|
|
@@ -82,16 +86,41 @@ onMount(() => {
|
|
|
82
86
|
}
|
|
83
87
|
};
|
|
84
88
|
});
|
|
89
|
+
// Keyboard handlers for panning (Space + drag)
|
|
90
|
+
function handleKeyDown(event) {
|
|
91
|
+
if (event.code === 'Space' && !isSpaceHeld) {
|
|
92
|
+
isSpaceHeld = true;
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function handleKeyUp(event) {
|
|
97
|
+
if (event.code === 'Space') {
|
|
98
|
+
isSpaceHeld = false;
|
|
99
|
+
isPanning = false;
|
|
100
|
+
panStart = null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
85
103
|
function handleMouseDown(event) {
|
|
86
104
|
if (!canvas || !image)
|
|
87
105
|
return;
|
|
88
106
|
if ('button' in event && event.button !== 0)
|
|
89
107
|
return;
|
|
90
108
|
const coords = getEventCoords(event);
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
// Start panning if space is held
|
|
111
|
+
if (isSpaceHeld) {
|
|
112
|
+
isPanning = true;
|
|
113
|
+
panStart = {
|
|
114
|
+
x: coords.clientX,
|
|
115
|
+
y: coords.clientY,
|
|
116
|
+
offsetX: viewport.offsetX,
|
|
117
|
+
offsetY: viewport.offsetY
|
|
118
|
+
};
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
91
121
|
const imagePoint = toImageCoords(coords.clientX, coords.clientY);
|
|
92
122
|
if (!imagePoint)
|
|
93
123
|
return;
|
|
94
|
-
event.preventDefault();
|
|
95
124
|
if (currentTool === 'eraser') {
|
|
96
125
|
// Find and remove annotation at click point
|
|
97
126
|
const canvasPoint = { x: coords.clientX, y: coords.clientY };
|
|
@@ -136,9 +165,20 @@ function handleMouseDown(event) {
|
|
|
136
165
|
// This absorbs micro-movements of the mouse
|
|
137
166
|
const MIN_POINT_DISTANCE = 3;
|
|
138
167
|
function handleMouseMove(event) {
|
|
168
|
+
const coords = getEventCoords(event);
|
|
169
|
+
// Handle panning
|
|
170
|
+
if (isPanning && panStart && onViewportChange) {
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
const dx = coords.clientX - panStart.x;
|
|
173
|
+
const dy = coords.clientY - panStart.y;
|
|
174
|
+
onViewportChange({
|
|
175
|
+
offsetX: panStart.offsetX + dx,
|
|
176
|
+
offsetY: panStart.offsetY + dy
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
139
180
|
if (!isDrawing || !currentAnnotation || !canvas || !image)
|
|
140
181
|
return;
|
|
141
|
-
const coords = getEventCoords(event);
|
|
142
182
|
const imagePoint = toImageCoords(coords.clientX, coords.clientY);
|
|
143
183
|
if (!imagePoint)
|
|
144
184
|
return;
|
|
@@ -165,6 +205,12 @@ function handleMouseMove(event) {
|
|
|
165
205
|
}
|
|
166
206
|
}
|
|
167
207
|
function handleMouseUp(event) {
|
|
208
|
+
// Stop panning
|
|
209
|
+
if (isPanning) {
|
|
210
|
+
isPanning = false;
|
|
211
|
+
panStart = null;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
168
214
|
if (!isDrawing || !currentAnnotation) {
|
|
169
215
|
isDrawing = false;
|
|
170
216
|
return;
|
|
@@ -295,12 +341,15 @@ let currentAnnotationCanvas = $derived.by(() => {
|
|
|
295
341
|
<svelte:window
|
|
296
342
|
onmousemove={handleMouseMove}
|
|
297
343
|
onmouseup={handleMouseUp}
|
|
344
|
+
onkeydown={handleKeyDown}
|
|
345
|
+
onkeyup={handleKeyUp}
|
|
298
346
|
/>
|
|
299
347
|
|
|
300
348
|
<!-- Overlay -->
|
|
301
349
|
<div
|
|
302
350
|
bind:this={containerElement}
|
|
303
351
|
class="annotation-tool-overlay"
|
|
352
|
+
class:panning={isSpaceHeld}
|
|
304
353
|
onmousedown={handleMouseDown}
|
|
305
354
|
role="button"
|
|
306
355
|
tabindex="-1"
|
|
@@ -565,6 +614,14 @@ let currentAnnotationCanvas = $derived.by(() => {
|
|
|
565
614
|
user-select: none;
|
|
566
615
|
}
|
|
567
616
|
|
|
617
|
+
.annotation-tool-overlay.panning {
|
|
618
|
+
cursor: grab;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.annotation-tool-overlay.panning:active {
|
|
622
|
+
cursor: grabbing;
|
|
623
|
+
}
|
|
624
|
+
|
|
568
625
|
.annotation-tool-svg {
|
|
569
626
|
width: 100%;
|
|
570
627
|
height: 100%;
|
|
@@ -732,7 +789,6 @@ let currentAnnotationCanvas = $derived.by(() => {
|
|
|
732
789
|
}
|
|
733
790
|
|
|
734
791
|
.control-group .value {
|
|
735
|
-
color: var(--primary-color, #63b97b);
|
|
736
792
|
font-weight: 600;
|
|
737
793
|
}
|
|
738
794
|
|