Vuetify + VeeValidate でダイアログを使った際のレイアウト崩れへの対処

先に作っていた Vuetify を使ったダイアログに対して VeeValidate を組み込んだところ、レイアウト崩れが発生したため、それに対処したメモです。
2020.08.19

前回に引き続き VeeValidate ネタです。

現象

以下のような感じで <v-dialog>scrollable を設定した上で、<ValidationObserver><v-form> <v-card> を組み合わせると、 ダイアログのレイアウトが崩れます。

レイアウトが崩れるダイアログ

  <v-dialog v-model="dialog" scrollable max-width="300px">
        :
        :
    <ValidationObserver v-slot="{ invalid }" ref="obs" immediate>
      <v-form>
        <v-card>
          <v-card-title>Select Country</v-card-title>
          <v-divider></v-divider>
          <v-card-text style="height: 300px;">
            <ValidationProvider v-slot="{ errors, valid }" name="country" rules="required">
              <v-radio-group v-model="country" column :error-messages="errors" :success="valid">
                <v-radio label="Bahamas, The" value="bahamas"></v-radio>
                <v-radio label="Bahrain" value="bahrain"></v-radio>
                    :
                    :
              </v-radio-group>
            </ValidationProvider>
          </v-card-text>
          <v-divider></v-divider>
          <v-card-actions>
            <v-btn color="blue darken-1" text @click="dialog = false">Close</v-btn>
            <v-btn color="blue darken-1" text @click="dialog = false" :disabled="invalid">Save</v-btn>
          </v-card-actions>
        </v-card>
      </v-form>
    </ValidationObserver>
  </v-dialog>

VeeValidate 適用前:

VeeValidate適用前フォーム

VeeValidate 適用後:

VeeValidate適用後フォーム(未対策)

環境情報

  • Node.js: 12.18.2
  • Vue.js: 2.6.11
  • Vuetify: 2.3.3
  • VeeValidate: 3.3.7
  • Vue CLI: 4.4.6

原因

issues にも上がっていましたが、あらゆるパターンに対応しているわけではないので、その場合は自分で実装すべき、という回答でクローズされていました。

See #3713. It isn't realistic to support every possible combination, you'll have to implement it yourself.

HTMLを確認したところ、VeeValidate の <ValidationObserver> が発行する <span> が原因で、Vuetify が定義している CSS にマッチしなくなっていました。

対処方法1

VuetifyのCSSは、以下のように .v-dialog--scrollable<form> または .v-card が親子関係である前提となっています。

https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VDialog/VDialog.sass#L75

VDialog.sass

.v-dialog--scrollable,
.v-dialog--scrollable > form
  display: flex

  > .v-card
    display: flex
    flex: 1 1 100%
    flex-direction: column
    max-height: 100%
    max-width: 100%

    > .v-card__title,
    > .v-card__actions
      flex: 0 0 auto

    > .v-card__text
      backface-visibility: hidden
      flex: 1 1 auto
      overflow-y: auto

上記の <span> はこの間に挟まれる構造のため、scrollable 関係の CSS が効かなくなっていました。

そこで、親子ではなく子孫関係でも適用されるような別CSSを書いて適用することで、レイアウト崩れはなくなりました。

対処方法1の問題点

レイアウト崩れはなくなったのですが、Vuetify 標準とは別のCSSを定義したことにより将来的な変更に追従できなくなる、というリスクがあります。

そのため、保守性を重視したいシステムの場合はあまり取りたくない方法です。

対処方法2

方法1で最低限の保険は掛けた状態ですが、他に方法はないかと考え、改めて VeeValidate のドキュメントを確認したところ、なんとそのまんまの情報がありました。

A slim prop can be used to force the component to be renderless, by default it is set to false.

<ValidationObserver>slim を適用するだけで、レイアウト崩れはなくなりました。

VeeValidate適用後フォーム(対策済)

まずは一次ソースに当たれ、という原則を改めて思い知ることになりました…(Vuetify側でなんとかすべきと考えて、VeeValidate 側で対処されているという想像に至らなかった)。

例によって、上記確認出来るソースを以下リポジトリに置いています。