I'm assuming you now know how to add @vue/composition-api to your project so I won't waste any more time.
Step 1
Where ever you need to, in your template, just add the textarea element like so:
<template>
<!-- anything else you probably had -->
<textarea ref="myTextArea" v-model="content"></textarea>
</template>
Notice we added a ref
attribute. name it whatever you like. Don't forget the v-model.
Step 2
Create the composition function. I created mine inside the same component file I created the textarea in. You can even create a separate file for it if you like.
function useTextArea() {
const myTextArea = ref<HTMLTextAreaElement>([]); // remove <...> if JS
const content = ref("");
return { myTextArea, content };
}
If not done automatically by your editor, add the following import statement
import { createComponent, ref, watch } from "@vue/composition-api";
// remember 'createComponent' is not needed/used in javascript
Step 3
Expose these properties to your Vue template. We do that in the setup function's return statement
export default createComponent({
// ... other things you did.
setup(props, context) {
// ... some other stuff
const { myTextArea, content } = useTextArea();
// ... maybe some more stuff, I don't know
return {
// ... maybe other things are here too, your things
myTextArea,
content
};
}
}) as VueConstructor;
I usually add as VueContructor
just to stop the noise from eslint and vue-router complaining about the component's expected type. Not really a requirement.
Ok everything is neat now, there should be no errors but nothing works yet. We do however have a reference without having to use this.$refs
bacause obviously we don't have access to this
in our setup function. But through the magic of the composition api, the concept of refs is unified.
Step 4
Making it work. We go back to the useTextArea
function to add a couple of lines to watch for changes in the textarea's content and calculate the element's new height after that.
function useTextArea(){
// ... after variable declarations
watch(() => {
if (content.value.length === 0) return;
myTextArea.value.rows = 2; // depends on what you want initially
const styles = window.getComputedStyle(myTextArea.value);
const paddingTop = parseInt(styles.paddingTop);
const paddingBottom = parseInt(styles.paddingBottom);
const padding = paddingTop + paddingBottom;
const currentHeight = parseInt(styles.height) - padding;
const initialHeight =
(parseInt(styles.height) - padding) / myTextArea.value.rows;
const scrollHeight = myTextArea.value.scrollHeight - padding;
const newRows = Math.ceil(scrollHeight / initialHeight);
textArea.value.rows = newRows;
});
// ... before the return statement
}
Aaand done. You now have a textarea that adds or removes rows if there are any changes with it's value. You don't have to know off hand, the height of your element or the font size of the content, which can change anytime.
One concern I have is just the performance of the code inside the watch
function. If you have a better implementation please link it in the comments, I'd love to see different ideas around this. I've never enjoyed writing VueJS like this ever before.
Top comments (0)