skip to Main Content

I’m using Vuetify text-fields and want to display a tooltip containing the content if the content is greater than the field width (user needs to scroll). The tooltip should only appear on hover (default behaviour). I started with the following

(Playground)

<script setup lang="ts">
  import { ref, computed } from "vue";

  const currentValue = ref("");
  const textFieldComponent = ref<VTextField>();

  const isTextFieldCuttingOffContent = computed(() => {
    if (!textFieldComponent.value) {
      return false;
    }

    if (!currentValue.value) {
      return false;
    }

    return (
      textFieldComponent.value.$el.clientWidth <
      textFieldComponent.value.$el.scrollWidth
    );
  });
</script>

<template>
  <v-container style="width: 300px">
    <v-tooltip :text="currentValue" :disabled="!isTextFieldCuttingOffContent">
      <template v-slot:activator="{ props }">
        <div v-bind="props">
          <v-text-field
            ref="textFieldComponent"
            label="label goes here"
            v-model="currentValue"
          />
        </div>
      </template>
    </v-tooltip>
  </v-container>
</template>

I also tried to use a watcher instead of a computed prop (Playground)

The problem is that isTextFieldCuttingOffContent always returns false because clientWidth and scrollWidth are always equal. Do you have any ideas what’s wrong or missing?

2

Answers


  1. Checking the clientWidth and scrollWidth properties could be your issue. Timing may be the culprit here – specifically, the moment when you attempt to access them. The final dimensions of the element may not have been established yet or the rendering process may not be complete.

    Incorporating a reactive watcher can guarantee precise measurements by constantly checking the dimensions when currentValue undergoes a change. To implement this technique, here is a revised version of your code.

    <script setup lang="ts">
      import { ref, computed, watch } from "vue";
    
      const currentValue = ref("");
      const textFieldComponent = ref<VTextField>();
      const isTextFieldCuttingOffContent = ref(false);
    
      const checkTextOverflow = () => {
        if (!textFieldComponent.value) {
          return;
        }
    
        if (!currentValue.value) {
          isTextFieldCuttingOffContent.value = false;
          return;
        }
    
        const el = textFieldComponent.value.$el;
        isTextFieldCuttingOffContent.value = el.clientWidth < el.scrollWidth;
      };
    
      watch(currentValue, checkTextOverflow);
    </script>
    
    <template>
      <v-container style="width: 300px">
        <v-tooltip :text="currentValue" :disabled="!isTextFieldCuttingOffContent">
          <template v-slot:activator="{ props }">
            <div v-bind="props">
              <v-text-field
                ref="textFieldComponent"
                label="label goes here"
                v-model="currentValue"
              />
            </div>
          </template>
        </v-tooltip>
      </v-container>
    </template>
    

    Whenever the value of currentValue changes, the checkTextOverflow function is called to guarantee that the measurements of clientWidth and scrollWidth are taken after the DOM updates.

    Using a reactive watcher to modify the isTextFieldCuttingOffContent variable allows for precise identification of when the text field is truncating content. 🙂
    Hope this helps!

    Login or Signup to reply.
  2. The scrollWidth changes as a result of DOM manipulation. If you console.log the current scrollWidth of the input field, it will indeed change correctly in your code. However, the issue here is that these DOM data are not automatically updated in the Vue reactivity system.

    To retrieve the updated values, you can use nextTick(). However, it’s important to await the value of nextTick() to ensure that the DOM manipulation is completed. Therefore, you should call it within an async function. Using computed() is typically not suitable for this purpose. Instead, it would be better to check if the scrollWidth has changed only when the value of interest changes.

    To achieve this, you can use the watch() function. You specify which variable’s changes to observe and which function to execute when a change is detected. In our case, we will monitor the currentValue variable and execute an async function. So when the currentValue changes, the async function is executed, waits for the update using nextTick(), and then checks the difference between the new clientWidth and scrollWidth. The true/false value is then stored in a separate variable that can be referenced in your code.

    <script setup lang="ts">
      import { ref, watch, nextTick } from "vue";
    
      const currentValue = ref("");
      const textFieldComponent = ref<VTextField>();
    
      // store boolean for disabled checking
      const isTextFieldCuttingOffContent = ref(false);
    
      // checks if the current scrollWidth of the input field is wider than the clientWidth
      const checkTextOverflow = async () => {
        await nextTick();
    
        const inputField = ref("inputField");
        const textContainer = ref("textContainer");
    
        const inputWidth = textFieldComponent.value.clientWidth;
        const textWidth = textFieldComponent.value.scrollWidth;
    
        isTextFieldCuttingOffContent.value = textWidth > inputWidth;
      };
    
      // call checkTextOverflow() function when currentValue changed
      watch(currentValue, checkTextOverflow);
    </script>
    
    <template>
      <v-container style="width: 300px">
        <v-tooltip :text="currentValue" :disabled="!isTextFieldCuttingOffContent">
          <template v-slot:activator="{ props }">
            <div v-bind="props">
              <v-text-field
                id="here"
                ref="textFieldComponent"
                label="label goes here"
                v-model="currentValue"
              />
            </div>
          </template>
        </v-tooltip>
      </v-container>
    </template>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search