@@ -46,6 +46,7 @@ const editingTextAnchor = ref({ x: 0, y: 0 });
4646const editingTextContent = ref ([' ' ]);
4747const editingCaret = ref ({ row: 0 , col: 0 });
4848const fontSize = ref (16 );
49+ const editingTextRegistered = ref (false );
4950
5051const 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+
8992function 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+
183193function 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+
196233function 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+
238281function 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
473517function 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+
483531function 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+
502552watch (mode, () => {
503553 if (! props .active ) return ;
504554 disableDrawing ();
@@ -572,6 +622,7 @@ onMounted(() => {
572622});
573623
574624onBeforeUnmount (() => {
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