🌟

Vue Datepicker を短絡的に使用する

2024/04/11に公開

課題意識

VueのUIフレームワークで人気があるのは、特に日本で人気があるのは「Vuetify」であろう。
けれど、Vuetify3はまだ発展途上であり、Vuetify2のようなカレンダーや、十分な機能を持ったDate(等)Pickerはない。

というわけで「Vue Datepicker」を使用することになる。
以下が期待される状態であるが、

こうなったり、

こうなったりする

これはなんとかせねばならない。

問題

問題1:下が切れる

良く起こりうるのが以下のピッカーの一部が切れてしまう問題だ。

原因としては、例えばVuetifyの「VOverlay」のコンテンツが、恐らく「overflow: hidden」的な要素を含んでおり、つまり、はみ出した分は表示しない、という点と、
Vue Datepickerが参照しているのが画面全体のサイズであって、表示領域が下に十分になければ上側に表示すればいいのだが、そうしてくれない、
更には、ターゲットの上側に表示するか下側に表示するかは自動になっており、ユーザが指定できない、といったあたりにあるだろう

対処法

そんな時用に用意されているのが「teleport」要素である。
簡単に言えば、親要素を明示的に示して、「overflow: visible」等の要素に飛ばしてあげればよいという話である。
因みに、その初期値は「body」となっていて、単に「:teleport="true"」のように指定するとbodyが親要素になる(多分)。

問題2:変なところに飛んでく

という解決法を示したのだが、デフォルトのまま使用すると、私の環境では、何故か以下のように、別の要素にくっつく、、、なんで??
正直理由はよくわからないが、正にテレポートというかどこいくねーん、からの壁の中にいる、状態である。

Teleportの復習

ここで、Teleportについて学んでおかなければならない、少し長くなるぞ(嘘)。
Vue DatepickerのTeleportの説明はあまりに少なく、正直意味不明なのだが、何故かというと、このTeleportはVueの持つ機能であるからだろう。そうであればそうだと書いて欲しいのだが、Vueのドキュメントくらい全部しっかり読んでるよね、と、そういうことなのだろう。

詳細は以下をどうぞ。
Vue テレポート

対処法

さて、上の記事を読んだあなたにはもうTeleportの使い方がわかっていると思うが、まあ以下のように使うものだ。

<div id="teleport" style="z-index: 3000;">

<v-dialog>
    <vue-date-picker v-model="date" locale="ja" teleport="#teleport" allowPreventDefault auto-apply format="yyyy/MM/dd" model-type="yyyy-MM-dd" :enable-time-picker="false" week-start="0"/>
</v-dialog>

「Vue Datepicker」を使用するとプロパティ?が長ったらしくなって面倒ではあるが、重要なのは「id="teleport"」を設定したDivを「teleport="#teleport"」で親要素にしているところだ。
因みに、テレポート先の要素(DOM)はテレポート時に存在していないといけないので、同じダイアログ内に設置したらエラーとなる。

で、これを今回タイトルの通りに短絡的に解決するとする、つまり、以下のようにした。

components/my/Teleport.vue
<script lang="ts">
export default defineComponent({
  layout: false,
  name: "Teleport"
});
</script>
<script setup lang="ts"></script>
<template>
  <div id="teleport"/>
</template>
<style scoped lang="scss">
#teleport {
  position: fixed;
  width: 0;
  height: 0;
  z-index: 3000;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: 0;
  overflow: visible;
}
</style>
app.vue
<script setup lang="ts">
</script>
<template>
  <v-app>
    <Html lang="ja"/>
    <l-app-bar/>
    <v-main style="background: #FAFAFA;">
      <my-teleport/>
      <NuxtPage/>
    </v-main>
    <l-footer/>
  </v-app>
</template>
<style scoped lang="scss"></style>
<v-dialog>
    <vue-date-picker v-model="date" teleport="#teleport" locale="ja" allowPreventDefault/>
</v-dialog>

というわけで、Vue Datepickerの親になることだけを強いられた「Teleport」なるサイズを持たぬ適当に「z-index:3000」に設定されたコンポーネントを用意することで対処した。
取り合えず上手く動いているので良いだろう。

が、何故親が「body」だと位置を計算し損ねるのだろう。
「VSelect」もテレポートを使用していて競合したりするのだろうか??

Discussion