🗂

vee-validateを使ってinvalidのinput要素にfocusする方法

2023/03/12に公開

はじめに

タイトルの通り、vee-validateを使ってinvalidになった(不正な値が入力された)input要素にfocusをしたかったです。

vee-validateを使うと、デフォルトでinvalidになったinput要素を赤枠で囲ってくれるが、それだけだとユーザ体験としてよろしくないform(※)があったからです。

※:formが縦に長く、画面内に全てのinput要素が収まらず、invalidになった要素がどこにあるのかわからないform

まずは公式ドキュメントを読む

まず公式ドキュメントを読みましたが、公式として提供はしていなそう。。

ということで自分で実装しました

ValidationObserverをラップした形で拡張していきます。

<template>
  <ValidationObserver v-bind="$attrs" ref="validationObserver">
    <slot v-bind:passes="passes"></slot>
  </ValidationObserver>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
// 指定した要素までスクロールさせるUtilメソッドです
import { scrollTo } from "@/utils/scrollUtil";

@Component
export default class CustomValidationObserver extends Vue {
  passes = async (action: Function) => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const isValid = await (this.$refs.validationObserver as any).validate();
    if (!isValid) {
      const element = document.querySelector(".is-invalid") as HTMLElement;
      if (!element) {
        return;
      } else if (element.nodeName === "SELECT") {
        this.focusSelectElement(element);
      } else {
        element.focus();
      }
      return;
    }
    await action();
  };

  // 通常のfocusだとプルダウンの選択肢が全面表示されるため、本メソッドで他input要素と同様のfocusの挙動を行う
  focusSelectElement = (element: HTMLElement) => {
    element.classList.add("focus");
    scrollTo(window, element, { offset: -90, duration: 0 });

    // デフォルトの「他の要素をクリックした際にフォーカスが外れる挙動」を再現するためにイベントを追加
    document.addEventListener("click", () => this.removeFocusClass(element), {
      once: true
    });
  };

  removeFocusClass = (element: HTMLElement) => {
    // 保存ボタン押下時のclickイベントで意図せずリスナーが実行されるため、イベントリスナー内で再度リスナーを登録
    // 保存ボタン押下の次のclickイベント時に、focusクラスをremoveするリスナーを実行させる
    document.addEventListener(
      "click",
      () => {
        element.classList.remove("focus");
      },
      {
        once: true,
        capture: true
      }
    );
  };
}
</script>

<style lang="scss">
.focus {
  border-color: #d23e36;
  box-shadow: 0 0 0 3px #f4cccc;
}
</style>

ポイント

  • invalid要素にはis-invalidクラスが付与されるので、const element = document.querySelector(".is-invalid") as HTMLElement;で対象要素を取得しています。

補足

  • iOS, Androidでは、select要素にfocusをした場合、自動でプルダウンが開いてしまうため、手動で赤枠 + 対象の要素までスクロールすることで擬似的にfocusさせるようにしています。
  • ↑と同じくselect要素で擬似的なfocus処理を実現させるために、removeFocusClass()で他要素がクリックされた際に自動でinvalid要素の赤枠が外れる挙動を実現させています。

Discussion