0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TAB / Shift + TAB でインデントを上げ / 下げする実装

Posted at

はじめに

各種エディターやQiitaなどでもサポートされている、TAB もしくは Shift + TABによるインデントを上げ下げする実装をしてみました。

[JS] Tabキーによる移動を禁止してTabを入力する。
※ベース部分の実装は上記の記事を参考にさせていただきました。ありがとうございました🙇‍♂️
※また、今回はvueで実装してます。

サンプル

  • 今回作成したコンポーネント
CommonTextArea.vue
<template>
  <textarea
    :id="uid"
    :value="value"
    :placeholder="label"
    @input="$emit('input', $event.target.value)"
  />
</template>

<script lang="ts">
import { Component, Prop, Vue } from "nuxt-property-decorator"

@Component
export default class CommonTextArea extends Vue {
  /* ===============================
   * props
   * =============================== */
  @Prop({ type: String, default: "" })
  value!: string

  @Prop({ type: String, default: "" })
  label!: string

  /* ===============================
   * lifecycle
   * =============================== */
  mounted() {
    // keyupだと移動後に処理が発火してしまうので注意。
    document.addEventListener("keydown", this.detectTabKey)
    this.uid = this.generateUid()
  }

  beforeDestroy() {
    document.removeEventListener("keydown", this.detectTabKey)
  }

  /* ===============================
   * methods
   * =============================== */
  /** tabキーを押下時の処理を追加 */
  detectTabKey(event: KeyboardEvent) {
    // TODO `value`などが使えないため`any`(適切な型ご存知な型いましたらぜひ教えてください・・!)
    const currentElement = document.activeElement as any
    // DOM取得できないか、異なるidのテキストエリアの場合か、textarea以外の場合は処理しない。
    if (!currentElement || currentElement.id !== this.uid || currentElement.tagName !== "TEXTAREA") return
    // shift + tab の場合
    if (event.shiftKey && event.key === "Tab") {
      const TAB = "  " // 「"\t"」にするかはお好みで。
      // 移動処理を無効
      event.preventDefault()
      const value = currentElement.value
      // sPosから現在の行数を取得する
      const sPos = currentElement.selectionStart
      const sValue = value.slice(0, sPos)
      const sValueList = sValue.split("\n")
      const currentRow = sValueList.length
      const currentIdx = currentRow - 1
      // 現在の行の値を取得
      const valueList = value.split("\n")
      const currentRowValue = valueList[currentIdx]
      // TABが含まれていなければ処理終了
      if (!currentRowValue.startsWith(TAB)) return
      // TABが含まれていたら除去して値を更新
      valueList[currentIdx] = currentRowValue.replace(TAB, "")
      const result = valueList.join("\n")
      this.$emit("input", result) // vueのdataにも反映
      currentElement.value = result
      // position移動のための処理
      // 対象行でカーソル位置までにTABが含まれているかで処理が変わる
      const sCurrentRowValue = sValueList[currentIdx]
      const isIncludeTab = sCurrentRowValue.startsWith(TAB)
      const cPos = sPos - (isIncludeTab ? TAB.length : 0)
      currentElement.setSelectionRange(cPos, cPos)
      return
    }
    // tab の場合
    if (event.key === "Tab") {
      event.preventDefault()
      const TAB = "  "
      const value = currentElement.value
      const sPos = currentElement.selectionStart
      const ePos = currentElement.selectionEnd
      const result = value.slice(0, sPos) + TAB + value.slice(ePos)
      const cPos = sPos + TAB.length
      this.$emit("input", result) // vueのdataにも反映
      currentElement.value = result
      currentElement.setSelectionRange(cPos, cPos)
    }
  }

  generateUid() {
    // 他のコンポーネントと区別がつくようにuidをつくる
    return Math.random().toString(32).substring(2)
  }
}

  • 親コンポーネント
Parent.vue
<template>
  <CommonTextArea v-model="hogeValue" label="内容" />
</template>

呼び出し部分のみ抜粋して書いておりますが、親側からはv-modelを使って呼び出せるようにしてあります😊

おわりに

どなたかの参考になれば幸いです!(もっとよい書き方があればぜひアドバイスお願いします!!)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?