Float label with Vuetify Textarea not resetting above Textarea after pasting and blur

267 Views Asked by At

I am having a Textarea blur problem that fails to reposition a floating label after clicking out out of the Textarea after a paste.

I PASTE some text into a Textarea and exit the Textarea in any way (Ctrl-Enter or click outside the area), which then causes the Textarea to Blur, the LABEL (e.g. Summary Annotation) does not move out of the Textarea as the label should. Instead, it stays put and overlaps the pasted text.

Only if I make a manual keystroke BEFORE I leave the Textarea will the label return to it's original position ABOVE the Textarea when it is blurred.

enter image description here enter image description here

Is there a direct way of solving this? Under other circumstances (non VUE 3), I would try to manually trigger the Textarea, but I don't think I can do that with a VUE DOM.

Here is the paste routine:

const textAreaPaste = (e: ClipboardEvent) => {
let clipboardData, pastedData;
let textResult = "";
const target = e.target as HTMLTextAreaElement;

if (e) {
    e.preventDefault();
    nextTick(() => {
        // Get pasted data via clipboard API
        clipboardData = e.clipboardData;
        pastedData = clipboardData ? clipboardData.getData("Text") : "";

        //Remove carriage returns
        const t1 = pastedData.replace(/\r(?!\n)|\n(?!\r)/g, "\n");

        // Insert the text in the correct editing position, if necessary
        if (e.target && (target.selectionStart || String(target.selectionStart) === "0")) {
            var startPos = target.selectionStart;
            var endPos = target.selectionEnd;
            textResult = target.value.substring(0, startPos) + t1 + target.value.substring(endPos, target.value.length);
        } else {
            textResult += t1;
        }
        // Truncate to max length
        (e.target as HTMLInputElement).value =
            textResult.length > maxAnnoLetter ? textResult.substr(0, maxAnnoLetter) : textResult;
        const attribs = (e.target as HTMLInputElement).attributes;
        const id = (attribs as HTMLAttributes).id;
        // Timeout on update necessary
        if (id) {
            setTimeout(() => {
                calcRemainingChars(id.nodeValue, textResult);
            }, 250);
        }
    });
}

};

2

There are 2 best solutions below

1
Norm Strassner On

Well, I found the answer. I did NOT need to call preventDefault on the event. So often it is one overlooked line.

Without calling preventDefault, the Textarea works as advertised.

1
Moritz Ringler On

May I suggest, instead of working with the godawful paste event, you can put your functionality into an @input listener. The operations you do in your handler (replacing \r\n and cutting length) can be done there as well, without direct DOM manipulations.

Set the handler by using the :value prop and the @input listener instead of v-model:

<v-textarea
  :value="textValue"
  @input="$event => textValue = adjustAfterPaste($event)"
></v-textarea>

You can check if the input comes from a paste by checking if the event's inputType is "insertFromPaste":

const adjustAfterPaste = (e) => {
  const value = e.target.value
  return (e.inputType !== 'insertFromPaste') ? value : value
    .replace(/\r(?!\n)|\n(?!\r)/g, "\n")
    .substring(0, maxlength);
}

Upsides seem glaringly obvious (not having to work with an unchangeable event, no direct DOM manipulations, not having to perform the paste manually, clear structure, brevity). But there are two downsides to this approach:

  • The replacement will be run on the whole string, not just on the pasted content, unless you figure out the pasted content by comparing the old and the new value
  • As with all other current approaches to change pasted content, it breaks undo/redo

Here is a snippet, I am using toUpperCase() to make the change more visible. Again, note that the change is applied to the whole string:

const { createApp, ref } = Vue;
const { createVuetify } = Vuetify
const vuetify = createVuetify()

const adjustAfterPaste = (e, maxlength) => {
  const value = e.target.value
  return (true || e.inputType !== 'insertFromPaste') ? value : value
    .toUpperCase()
    .substring(0, maxlength);
}

const App = {
  setup(){
    return {
      textValue: ref(''),
      maxlength: ref(50),
      adjustAfterPaste,
    }
  }
}
createApp(App).use(vuetify).mount('#app')
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.css" />
<link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
<div id="app">
  <v-app>
    <v-main>
      <v-textarea
        label="textarea"
        :value="textValue"
        @input="textValue = adjustAfterPaste($event)"
        :maxlength="maxlength"
      ></v-textarea>
       Content: {{ textValue }}
    </v-main>
  </v-app>

</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.js"></script>


Note that automatically cutting text after paste leads to users not recognizing it and sending cropped data. It is usually preferable to let them paste and mark the field as invalid.