4
votes

Basically, I'm creating a form component that is contained inside a v-dialog. The form component will have different child components that are rendered based on select input. So I have to set width of v-dialog to "unset", so that the width of the dialog will stretch to match its content.

The transition works when I toggle the value of width, eg: either 450px or 300px. The problem is that I don't know beforehand the width of the form contains in the dialog, so I definitely need to use dynamic width.

So far, I can not find anyways to achieve transition when using dynamic width. I was trying to get the width of the form component using refs, but setting width to unset, prevent the transition. By the way, the transition I'm talking about is the transition of the width, when using fixed width, it shows nice transition but not for dynamic width

<div id="app">
  <v-app id="inspire">
    <div class="text-center">
      <v-dialog v-model="dialog" width="unset">
        <template v-slot:activator="{ on }">
          <v-btn color="red lighten-2" dark v-on="on">
            Click Me
          </v-btn>
        </template>
        <v-card>
          <v-select v-model="selectedForm" :items="items">
          </v-select>
          <div v-if="selectedForm==='form-a'" class='form-a'>FormA</div>
          <div v-if="selectedForm==='form-b'" class='form-b'>FormB</div>
          <v-card-actions>
            <v-spacer></v-spacer>
            <v-btn color="primary" text @click="dialog = false">
              I accept
            </v-btn>
          </v-card-actions>
        </v-card>
      </v-dialog>
    </div>
  </v-app>
</div> 

new Vue({
  el: "#app",
  vuetify: new Vuetify(),
  data() {
    return {
      selectedForm: "form-a",
      items: ["form-a", "form-b"],
      dialog: false
    };
  }
});

codepen for using fixed width: https://codepen.io/duongthienlee/pen/MWaBLXm
codepen for using dynamic width: https://codepen.io/duongthienlee/pen/GRpBzmL

Noted that in the example i made in codepen, I defined width already, but the real case is that I don't know beforehand the width of form-a and form-b component. form-a and form-b width will be inherited by its parent div which is v-dialog, so that's why I set the width of v-dialog to be unset. An example of what I mean "dynamic width": form-a has a select input. When user chooses an item, there will be a request to server to get input labels. So form-a will render multiple input fields based on the response body from server. The response body will contain label and default values information. So that makes the width of form-a becomes dynamic.

4

4 Answers

2
votes

I think something like this can work for you.

Change v-dialog like so:

<v-dialog v-model="dialog" :width="forms.find(x => x.name===selectedForm).width">

Modify data() to return a forms prop:

data() {
    return {
      selectedForm: "form-a",
      items: ["form-a", "form-b"],
      dialog: false,
      forms: [
        {
          name: 'form-a',
          width: 200
        },
        {
          name: 'form-b',
          width: 1000
        }
      ]
    };
  }
1
votes

What you want to do is get the size of the rendered form, and then apply it to the dialog. This is a common theme when attempting to animate content with dynamic dimensions. One way to do this is by:

  1. Set the form's visibility as hidden
  2. Wait for it to render
  3. Get the form's width and set it to the dialog
  4. Unset the form's visibility

The tricky/hacky part is that you have to properly await DOM (setTimeout) and Vue ($nextTick) recalculations. I didn't have to await for Vue's $nextTick in this example, but you probably will if you're rendering nested form components:

<div class="form-container">
  <div :style="formStyle('form-a')" class='form-a' ref="form-a">FormA</div>
  <div :style="formStyle('form-b')" class='form-b' ref="form-b">FormB</div>
</div>
computed:{
  formStyle(){
    return form => ({
      visibility: this.selectedForm == form ? 'inherit' : 'hidden',
      position: this.selectedForm == form ? 'inherit' : 'absolute'
    })
  }
},
methods: {
  async onSelectChange(form){
    // async request
    await new Promise(resolve => setTimeout(resolve, 1000))
    this.selectedForm = form
    this.recalculate()
  },
  async recalculate(){
    // wait for DOM to recalculate
    await new Promise(resolve => setTimeout(resolve))
    const formEl = this.$refs[this.selectedForm]
    this.dialogWidth = formEl.clientWidth
    this.dialogHeight = formEl.clientHeight
  },
  ...
}

Here's the working code to give you an idea: https://codepen.io/cuzox/pen/yLYwoQo

0
votes

If I understand you correctly, then this can be done using css. You can try replace all the fix width in the form with

  width: fit-content;

For example in you codepen:

.form-a {
  width: fit-content;
  height: 350px;
  background: blue;
}
.form-b {
  width: fit-content;
  height: 500px;
  background: red;
}
0
votes

The v-dialog renders into a div with class v-dialog: v-dialog width set to 300 or 450

v-dialog width set to unset

It seems the animation only works when the the width is of known value, so it cannot be just "unset". The solution would be to get the width of the child element, and set the width of the v-dialog accordingly with a variable.

See VueJS get Width of Div on how to get the width of the child element.

Let me know if it works, I find this is very interesting.