Skip to content

Commit 5ceaaea

Browse files
committed
Fix - PenAndPaper - Fix caret not showing on built-in annotator in text mode
1 parent c0b4f68 commit 5ceaaea

File tree

1 file changed

+75
-37
lines changed

1 file changed

+75
-37
lines changed

src/atoms/PenAndPaper.vue

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const editingTextAnchor = ref({ x: 0, y: 0 });
4646
const editingTextContent = ref(['']);
4747
const editingCaret = ref({ row: 0, col: 0 });
4848
const fontSize = ref(16);
49+
const editingTextRegistered = ref(false);
4950
5051
const cursorDraw = ref(`url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAABg2lDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TpSIVh2YQcchQnSyIijhKFYtgobQVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg+AHi6OSk6CIl/i8ptIjx4Lgf7+497t4BQrPKNKtnAtB020wn4lIuvyqFXhGGiAhCiMnMMpKZxSx8x9c9Any9i/Es/3N/jgG1YDEgIBHPMcO0iTeIZzZtg/M+scjKskp8Tjxu0gWJH7muePzGueSywDNFM5ueJxaJpVIXK13MyqZGPE0cVTWd8oWcxyrnLc5atc7a9+QvDBf0lQzXaY4ggSUkkYIEBXVUUIWNGK06KRbStB/38Q+7/hS5FHJVwMixgBo0yK4f/A9+d2sVpya9pHAc6H1xnI9RILQLtBqO833sOK0TIPgMXOkdf60JzH6S3uho0SNgcBu4uO5oyh5wuQMMPRmyKbtSkKZQLALvZ/RNeSByC/Sveb2193H6AGSpq+Ub4OAQGCtR9rrPu/u6e/v3TLu/H5C7crM1WjgWAAAABmJLR0QAqwB5AHWF+8OUAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5gwUExIUagzGcQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAABfSURBVBjTldAxDoNQDIPhL0+q1L33P1AvAhN7xfK6WAgoLfSfrNiykpQtE+7RLzx2vgF9D3o8lWDmn1QVVMP0LZQGmNtqp1/cmou0XHdG/+sYeGZwFBqPCub8rkcvvAGvsi1VYarR8wAAAABJRU5ErkJggg==') 5 5, auto`);
5152
@@ -57,6 +58,7 @@ function startSvgTextEditing(event) {
5758
editingTextAnchor.value = { x, y };
5859
editingTextContent.value = [''];
5960
editingCaret.value = { row: 0, col: 0 };
61+
editingTextRegistered.value = false;
6062
6163
const textNode = document.createElementNS("http://www.w3.org/2000/svg", "text");
6264
textNode.setAttribute("x", x);
@@ -86,6 +88,7 @@ function startSvgTextEditing(event) {
8688
drawSvgCaret();
8789
}
8890
91+
8992
function handleSvgTextKeydown(e) {
9093
if (!isEditingText.value) return;
9194
let { row, col } = editingCaret.value;
@@ -103,17 +106,15 @@ function handleSvgTextKeydown(e) {
103106
e.preventDefault();
104107
} else if (e.key === 'Backspace') {
105108
if (col > 0) {
106-
// Delete char before caret
107109
lines[row] = lines[row].slice(0, col - 1) + lines[row].slice(col);
108110
col -= 1;
109111
updated = true;
110112
} else if (row > 0) {
111-
// Merge with previous line
112-
const prevLen = lines[row - 1].length;
113+
const previousLength = lines[row - 1].length;
113114
lines[row - 1] += lines[row];
114115
lines.splice(row, 1);
115116
row -= 1;
116-
col = prevLen;
117+
col = previousLength;
117118
updated = true;
118119
}
119120
e.preventDefault();
@@ -168,18 +169,27 @@ function handleSvgTextKeydown(e) {
168169
cleanupSvgTextEditing(true);
169170
return;
170171
} else if (e.key === 'Tab') {
171-
// TODO: manage Tab ? Not sure it's that important
172172
e.preventDefault();
173173
}
174174
175175
if (updated) {
176176
editingTextContent.value = lines;
177177
editingCaret.value = { row, col };
178+
179+
// As soon as there is some content and this text is not yet registered add it to the undo stack and clear the redo stack
180+
const hasContent = lines.some(line => line.length > 0);
181+
if (hasContent && !editingTextRegistered.value && editingTextNode.value) {
182+
stack.value.push(editingTextNode.value);
183+
redoStack.value = [];
184+
editingTextRegistered.value = true;
185+
}
186+
178187
updateSvgTextDisplay();
179188
drawSvgCaret();
180189
}
181190
}
182191
192+
183193
function updateSvgTextDisplay() {
184194
const textNode = editingTextNode.value;
185195
const { x } = editingTextAnchor.value;
@@ -193,19 +203,49 @@ function updateSvgTextDisplay() {
193203
});
194204
}
195205
206+
const caretBlinkTimer = ref(null);
207+
208+
function stopCaretBlink() {
209+
if (caretBlinkTimer.value !== null) {
210+
clearInterval(caretBlinkTimer.value);
211+
caretBlinkTimer.value = null;
212+
}
213+
}
214+
215+
function startCaretBlink(caretEl) {
216+
// reset any previous timer
217+
stopCaretBlink();
218+
219+
let visible = true;
220+
caretEl.style.opacity = '1';
221+
222+
caretBlinkTimer.value = setInterval(() => {
223+
// if caret was removed from the DOM, stop
224+
if (!G.value || !caretEl || !G.value.contains(caretEl)) {
225+
stopCaretBlink();
226+
return;
227+
}
228+
visible = !visible;
229+
caretEl.style.opacity = visible ? '1' : '0';
230+
}, 500); // 500ms = 1s period (blink)
231+
}
232+
196233
function drawSvgCaret() {
197-
const existingCaret = G.value.querySelector('.vue-data-ui-svg-caret');
198-
if (existingCaret) G.value.removeChild(existingCaret);
234+
const existingCaret = G.value?.querySelector('.vue-data-ui-svg-caret');
235+
if (existingCaret && G.value) {
236+
G.value.removeChild(existingCaret);
237+
}
199238
200239
const textNode = editingTextNode.value;
201-
if (!textNode) return;
240+
if (!textNode || !G.value) return;
202241
203242
const { x, y } = editingTextAnchor.value;
204243
const { row, col } = editingCaret.value;
205244
const fontPx = fontSize.value * props.scale;
206245
207246
const tspan = textNode.childNodes[row];
208247
if (!tspan) return;
248+
209249
let tempText = tspan.textContent.slice(0, col);
210250
if (tempText.endsWith(' ')) {
211251
tempText += '\u00A0';
@@ -221,20 +261,23 @@ function drawSvgCaret() {
221261
const bbox = measureText.getBBox();
222262
G.value.removeChild(measureText);
223263
224-
// Y offset
225-
let caretY = y + row * fontPx * 1.2;
226-
let caretX = x + bbox.width;
264+
const caretY = y + row * fontPx * 1.2;
265+
const caretX = x + bbox.width;
227266
228267
const caret = document.createElementNS("http://www.w3.org/2000/svg", "rect");
229268
caret.setAttribute("x", caretX);
230269
caret.setAttribute("y", caretY);
270+
caret.setAttribute("rx", 1);
231271
caret.setAttribute("width", 2);
232272
caret.setAttribute("height", fontPx);
233273
caret.setAttribute("fill", currentColor.value);
234274
caret.setAttribute("class", "vue-data-ui-svg-caret");
235275
G.value.appendChild(caret);
276+
277+
startCaretBlink(caret);
236278
}
237279
280+
238281
function handleSvgTextBlur(e) {
239282
if (!editingTextNode.value) return;
240283
@@ -254,8 +297,12 @@ function cleanupSvgTextEditing(remove = false) {
254297
window.removeEventListener('keydown', handleSvgTextKeydown);
255298
window.removeEventListener('mousedown', handleSvgTextBlur, true);
256299
257-
const caret = G.value.querySelector('.vue-data-ui-svg-caret');
258-
caret && G.value.removeChild(caret);
300+
stopCaretBlink();
301+
302+
const caret = G.value?.querySelector('.vue-data-ui-svg-caret');
303+
if (caret && G.value) {
304+
G.value.removeChild(caret);
305+
}
259306
260307
const tspans = editingTextNode.value?.children;
261308
let isEmpty = false;
@@ -265,19 +312,16 @@ function cleanupSvgTextEditing(remove = false) {
265312
}
266313
267314
if (remove || isEmpty) {
268-
if (editingTextNode.value && G.value.contains(editingTextNode.value)) {
315+
if (editingTextNode.value && G.value && G.value.contains(editingTextNode.value)) {
269316
G.value.removeChild(editingTextNode.value);
270317
}
271-
} else {
272-
if (editingTextNode.value && G.value.contains(editingTextNode.value)) {
273-
stack.value.push(editingTextNode.value);
274-
}
275318
}
276319
277320
isEditingText.value = false;
278321
editingTextNode.value = null;
279322
editingTextContent.value = [''];
280323
editingCaret.value = { row: 0, col: 0 };
324+
editingTextRegistered.value = false;
281325
}
282326
283327
@@ -472,14 +516,18 @@ function stopDrawing(event) {
472516
473517
function deleteLastDraw() {
474518
if (stack.value.length > 0) {
475-
const lastPath = stack.value.pop();
476-
redoStack.value.push(lastPath);
477-
if (G.value) {
478-
G.value.removeChild(lastPath);
519+
const lastShape = stack.value.pop();
520+
redoStack.value.push(lastShape);
521+
522+
if (lastShape === editingTextNode.value) {
523+
cleanupSvgTextEditing(true);
524+
} else if (G.value && G.value.contains(lastShape)) {
525+
G.value.removeChild(lastShape);
479526
}
480527
}
481528
}
482529
530+
483531
function redoLastDraw() {
484532
if (redoStack.value.length > 0) {
485533
const lastUndonePath = redoStack.value.pop();
@@ -496,9 +544,11 @@ function reset() {
496544
}
497545
stack.value = [];
498546
redoStack.value = [];
547+
editingTextRegistered.value = false;
499548
addInteractionMask();
500549
}
501550
551+
502552
watch(mode, () => {
503553
if (!props.active) return;
504554
disableDrawing();
@@ -572,6 +622,7 @@ onMounted(() => {
572622
});
573623
574624
onBeforeUnmount(() => {
625+
stopCaretBlink();
575626
if (G.value && props.svgRef) {
576627
G.value.remove();
577628
disableDrawing();
@@ -735,19 +786,6 @@ input[type="range"].vertical-range {
735786
736787
<style>
737788
.vue-data-ui-svg-caret {
738-
opacity: 0;
739-
animation: caret 1s linear infinite;
740-
}
741-
742-
@keyframes caret {
743-
0% {
744-
opacity: 0;
745-
}
746-
40% {
747-
opacity: 1;
748-
}
749-
100% {
750-
opacity: 0;
751-
}
789+
opacity: 1;
752790
}
753-
</style>
791+
</style>

0 commit comments

Comments
 (0)