Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change needed for Vue 3 (does not use $el) #35

Open
jijoel opened this issue Oct 7, 2020 · 7 comments
Open

Change needed for Vue 3 (does not use $el) #35

jijoel opened this issue Oct 7, 2020 · 7 comments

Comments

@jijoel
Copy link

jijoel commented Oct 7, 2020

In Vue 3, components are no longer limited to only 1 root element, so we will no longer have an $el. I am currently experimenting with Vue 3, and found that changing the component source like this will make it work:

@@ -2,6 +2,7 @@
   <textarea
     :style="computedStyles"
     v-model="val"
+    ref="textarea"
     @focus="resize"
   ></textarea>
 </template>
@@ -87,7 +88,7 @@ export default {
       const important = this.isHeightImportant ? 'important' : ''
       this.height = `auto${important ? ' !important' : ''}`
       this.$nextTick(() => {
-        let contentHeight = this.$el.scrollHeight + 1
+        let contentHeight = this.$refs.textarea.scrollHeight + 1

I don't know the other changes that will be needed to make a distributable version, but found that (for now) it does work to copy the TextareaAutosize.vue file (with the above changes) into my own code base, and call it like this:

<template>
    <TextareaAutosize v-model="text"></TextareaAutosize>
</template>

import TextareaAutosize from "../components/TextareaAutosize.vue"
export default {
    components: {TextareaAutosize},
}
@dacoto97
Copy link

+1

@issei-m
Copy link

issei-m commented Feb 2, 2022

@jijoel Thank you for providing the awesome workaround. I'd like to a bit make changes like:

--- a/TextareaAutoresize.vue  2022-02-02 16:31:23.000000000 +0900
+++ b/TextareaAutoresize.vue  2022-02-02 16:31:23.000000000 +0900
@@ -22,7 +22,7 @@
 export default {
   name: 'TextareaAutosize',
   props: {
-    value: {
+    modelValue: {
       type: [String, Number],
       default: ''
     },
@@ -78,7 +78,7 @@
     }
   },
   watch: {
-    value (val) {
+    modelValue (val) {
       this.val = val
     },
     val (val) {
@@ -123,7 +123,7 @@
     }
   },
   created () {
-    this.val = this.value
+    this.val = this.modelValue
   },
   mounted () {
     this.resize()

v-model is now bound to the prop modelValue instead of value since Vue 3.
So we need these changes to pre-set value to the textarea from the parent.

See: https://v3.vuejs.org/guide/migration/v-model.html

Best regards.

@karladler
Copy link

karladler commented Nov 10, 2022

in case somebody wants to use this component within a Vue3 typescript setup;

<template>
  <textarea
    ref="textareaRef"
    v-model="value"
    :style="computedStyles"
    @focus="resize"
  />
</template>

<script lang="ts">
import { defineComponent, computed, ref, nextTick, CSSProperties, onMounted, PropType, onUpdated } from 'vue'

type CssAttribs = 'resize'|'overflow'|'height';

export default defineComponent({
  props: {
    modelValue: {
      type: [String, Number],
      default: ''
    },
    autosize: {
      type: Boolean,
      default: true
    },
    minHeight: {
      type: [Number],
      required: false,
    },
    maxHeight: {
      type: [Number],
      required: false,
    },
    important: {
      type: [Boolean, Array] as PropType<CssAttribs[]|boolean>,
      default: false
    }
  },
  emits: ['update:modelValue'],
  setup (props) {
    const height = ref('0');
    const textareaRef = ref();
    const maxHeightScroll = ref(false);
    const value = ref(props.modelValue);

    const isImportantAttr = (attr: CssAttribs) => {
      return props.important === true
        || (Array.isArray(props.important) && props.important.includes(attr));
    }

    const computedStyles = computed<CSSProperties|undefined>(() => {
      if (!props.autosize) {
        return undefined;
      }

      return {
        resize: !isImportantAttr('resize') ? 'none' : 'none !important',
        height: height.value,
        overflow: maxHeightScroll.value
          ? 'auto'
          : (!isImportantAttr('overflow') ? 'hidden' : 'hidden !important')
      } as CSSProperties; // !important is not always allowed for `CSSProperties`
    });

    const resize = () => {
      const important = isImportantAttr('height') ? 'important' : ''
      height.value = `auto${important ? ' !important' : ''}`

      nextTick(() => {
        let contentHeight = textareaRef.value.scrollHeight + 1
        if (props.minHeight) {
          contentHeight = contentHeight < props.minHeight ? props.minHeight : contentHeight
        }
        if (props.maxHeight) {
          if (contentHeight > props.maxHeight) {
            contentHeight = props.maxHeight
            maxHeightScroll.value = true
          } else {
            maxHeightScroll.value = false
          }
        }
        const heightVal = `${contentHeight}px`
        height.value = `${heightVal}${important ? ' !important' : ''}`;
      });
    }

    onMounted(() => {
      resize();
    });

    return {
      textareaRef,
      resize,
      computedStyles,
      value,
      maxHeightScroll: false,
      height: 'auto'
    }
  },
  watch: {
    modelValue (val) {
      this.value = val
      this.$nextTick(this.resize)
      this.$emit('update:modelValue', val)
    },
    minHeight () {
      this.$nextTick(this.resize)
    },
    maxHeight () {
      this.$nextTick(this.resize)
    },
    autosize (val) {
      if (val) this.resize()
    }
  },
})
</script>

@hokkaido
Copy link

hokkaido commented Feb 23, 2023

@karladler

    modelValue (val) {
      this.value = val
      this.$nextTick(this.resize)
      this.$emit('update:modelValue', val)
    },

I think you want to watch value here as well, not just modelValue?

@mkantautas
Copy link

@hokkaido

Yes, in order for the component to work you need to add an extra watch for value:

    value(val) {
      this.$nextTick(this.resize)
      this.$emit('update:modelValue', val)
    },

@Lehoczky
Copy link

For vue3 I would recommend using vueuse | useTextareaAutosize

@shrpne
Copy link

shrpne commented Aug 23, 2024

Also, for Vue 3 you can use v-autosize, it's a Vue wrapper for battle-tested jackmoore/autosize.

It is implemented as a directive without any options, which is easier to use IMO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants