型定義

md2formは完全にTypeScriptで書かれており、型安全なフォーム処理を可能にします。このページでは、すべての型定義について詳しく説明します。

主要な型

FormDocument

パースされたフォーム全体を表すメインの型です。

type FormDocument = {
  title: string // フォームタイトル(必須)
  description?: string // フォーム説明文(任意)
  settings?: FormSettings // フォーム設定(任意)
  pages: Page[] // ページ(セクション)の配列
}

使用例:

import { parseMarkdownToForm } from "md2form"
import type { FormDocument } from "md2form"
 
const form: FormDocument = await parseMarkdownToForm(markdown)
console.log(form.title) // string
console.log(form.pages.length) // number

FormSettings

フロントマターで設定されるフォーム全体の設定を表す型です。

type FormSettings = {
  collectEmail?: boolean // メールアドレス収集
  allowMultipleResponses?: boolean // 複数回答許可
  limitResponses?: number | null // 回答数制限
  showProgressBar?: boolean // プログレスバー表示
  shuffleQuestions?: boolean // 質問順シャッフル
  themeColor?: string // テーマカラー
  backgroundImage?: string // 背景画像
  font?: string // フォント
  responseReceipt?: "always" | "never" | "whenRequested" // 受領通知
}

Page

フォーム内の1つのページ(セクション)を表す型です。

type Page = {
  title?: string // ページタイトル(任意)
  description?: string // ページ説明(任意)
  elements: FormElement[] // 質問要素の配列
}

FormElementBase

すべての質問要素が共通して持つ基本プロパティです。

type FormElementBase = {
  type: ElementType // 質問タイプ(必須)
  label?: string // 質問ラベル(任意)
  description?: string // 質問説明(任意)
  required?: boolean // 必須入力フラグ(任意)
  visible?: boolean // 表示フラグ(任意)
}

質問タイプの列挙型

type ElementType =
  | "short_text" // 短いテキスト入力
  | "long_text" // 長いテキスト入力
  | "number" // 数値入力
  | "email" // メールアドレス入力
  | "phone" // 電話番号入力
  | "dropdown" // ドロップダウン選択
  | "radio" // ラジオボタン選択
  | "checkbox" // チェックボックス選択
  | "date" // 日付選択
  | "time" // 時刻選択
  | "rating" // 星評価
  | "likert" // リッカート尺度
  | "matrix" // マトリクス(格子)
  | "file_upload" // ファイルアップロード
  | "section_header" // セクションヘッダー
  | "scale" // スケール(スライダー)
  | "signature" // 電子署名
  | "image" // 画像表示
  | "video" // 動画表示
  | "boolean" // Yes/No選択
  | "unknown" // 不明なタイプ(フォールバック)

FormElement ユニオン型

すべての質問要素を含むユニオン型です。

type FormElement =
  | ShortText
  | LongText
  | NumberField
  | EmailField
  | PhoneField
  | DropdownField
  | RadioField
  | CheckboxField
  | DateField
  | TimeField
  | RatingField
  | LikertField
  | MatrixField
  | FileUploadField
  | SectionHeader
  | ScaleField
  | SignatureField
  | MediaField
  | BooleanField
  | UnknownElement

詳細な質問タイプ定義

テキスト入力系

ShortText

type ShortText = FormElementBase & {
  type: "short_text"
  placeholder?: string // プレースホルダー
  maxLength?: number // 最大文字数
  default?: string // デフォルト値
}

LongText

type LongText = FormElementBase & {
  type: "long_text"
  placeholder?: string // プレースホルダー
  maxLength?: number // 最大文字数
  default?: string // デフォルト値
  richText?: boolean // リッチテキスト編集
}

NumberField

type NumberField = FormElementBase & {
  type: "number"
  placeholder?: string // プレースホルダー
  min?: number // 最小値
  max?: number // 最大値
  step?: number // 刻み値
  default?: number | null // デフォルト値
  integerOnly?: boolean // 整数のみ許可
}

EmailField

type EmailField = FormElementBase & {
  type: "email"
  placeholder?: string // プレースホルダー
  default?: string // デフォルト値
  allowMultiple?: boolean // 複数メールアドレス許可
}

PhoneField

type PhoneField = FormElementBase & {
  type: "phone"
  placeholder?: string // プレースホルダー
  countryCodeRequired?: boolean // 国コード必須
  default?: string // デフォルト値
}

選択系

type DropdownField = FormElementBase & {
  type: "dropdown"
  options: string[] // 選択肢
  allowOther?: boolean // その他選択肢許可
  multiple?: false // 複数選択(常にfalse)
  default?: string | null // デフォルト選択値
  searchable?: boolean // 検索機能有効化
}

RadioField

type RadioField = FormElementBase & {
  type: "radio"
  options: string[] // 選択肢
  allowOther?: boolean // その他選択肢許可
  default?: string | null // デフォルト選択値
}

CheckboxField

type CheckboxField = FormElementBase & {
  type: "checkbox"
  options: string[] // 選択肢
  minSelected?: number | null // 最小選択数
  maxSelected?: number | null // 最大選択数
  default?: string[] | null // デフォルト選択値配列
}

日時系

DateField

type DateField = FormElementBase & {
  type: "date"
  includeTime?: boolean // 時刻も含めるか
  minDate?: string // 最小日付(ISO形式)
  maxDate?: string // 最大日付(ISO形式)
  default?: string | null // デフォルト日付
}

TimeField

type TimeField = FormElementBase & {
  type: "time"
  minTime?: string // 最小時刻(HH:MM)
  maxTime?: string // 最大時刻(HH:MM)
  stepMinutes?: number // 刻み幅(分)
  default?: string | null // デフォルト時刻
}

評価・スケール系

RatingField

type RatingField = FormElementBase & {
  type: "rating"
  scale?: number // 評価段階数
  labels?: { low?: string; high?: string } // 最低・最高ラベル
  default?: number | null // デフォルト評価
  icon?: "star" | "heart" | "circle" // アイコンタイプ
}

LikertField

type LikertField = FormElementBase & {
  type: "likert"
  statements: string[] // 評価項目(行)
  scaleLabels: string[] // 評価尺度(列)
  requiredPerStatement?: boolean // 各項目必須評価
}

MatrixField

type MatrixField = FormElementBase & {
  type: "matrix"
  rows: string[] // 行ラベル
  columns: string[] // 列ラベル
  cellType?: "radio" | "checkbox" | "number" | "short_text" // セルタイプ
  requiredPerRow?: boolean // 各行必須入力
}

ScaleField

type ScaleField = FormElementBase & {
  type: "scale"
  min: number // 最小値
  max: number // 最大値
  step?: number // 刻み値
  minLabel?: string // 最小値ラベル
  maxLabel?: string // 最大値ラベル
  default?: number | null // デフォルト値
}

ファイル・署名系

FileUploadField

type FileUploadField = FormElementBase & {
  type: "file_upload"
  allowedTypes?: string[] // 許可ファイル形式
  maxFiles?: number // 最大ファイル数
  maxSizeMB?: number // 最大サイズ(MB)
}

SignatureField

type SignatureField = FormElementBase & {
  type: "signature"
  captureMode?: "draw" | "type" | "upload" // 署名方法
  required?: boolean // 必須署名
}

メディア・表示系

MediaField

type MediaField = FormElementBase & {
  type: "image" | "video"
  src: string // メディアURL
  alt?: string // 代替テキスト(imageのみ)
  width?: number | "auto" // 幅
  height?: number | "auto" // 高さ
  caption?: string // キャプション
}

SectionHeader

type SectionHeader = FormElementBase & {
  type: "section_header"
  title?: string // メインタイトル
  subtitle?: string // サブタイトル
}

その他

BooleanField

type BooleanField = FormElementBase & {
  type: "boolean"
  onLabel?: string // Trueの場合のラベル
  offLabel?: string // Falseの場合のラベル
  default?: boolean | null // デフォルト値
}

UnknownElement

type UnknownElement = FormElementBase & {
  type: "unknown" // 不明なタイプのフォールバック
}

便利な型エイリアス

TextInputElement

テキスト入力系要素のユニオン型:

type TextInputElement = ShortText | LongText | NumberField | EmailField | PhoneField

型ガードの実装

TypeScriptで型安全にフォーム要素を処理するための型ガード関数:

import type { FormElement, ShortText, NumberField, RatingField } from "md2form"
 
// 基本的な型ガード
function isShortText(element: FormElement): element is ShortText {
  return element.type === "short_text"
}
 
function isNumberField(element: FormElement): element is NumberField {
  return element.type === "number"
}
 
function isRatingField(element: FormElement): element is RatingField {
  return element.type === "rating"
}
 
// 汎用的な型ガード
function isElementOfType<T extends FormElement>(
  element: FormElement,
  type: T["type"],
): element is T {
  return element.type === type
}
 
// 使用例
form.pages.forEach((page) => {
  page.elements.forEach((element) => {
    if (isShortText(element)) {
      console.log(element.placeholder) // 型安全
    }
 
    if (isElementOfType<NumberField>(element, "number")) {
      console.log(element.min, element.max) // 型安全
    }
  })
})

実践的な型活用例

型安全なフォーム処理

import { parseMarkdownToForm } from "md2form"
import type { FormDocument, FormElement, ShortText, NumberField, CheckboxField } from "md2form"
 
// フォーム処理クラス
class FormProcessor {
  private form: FormDocument
 
  constructor(form: FormDocument) {
    this.form = form
  }
 
  // 型安全な要素抽出
  getElementsByType<T extends FormElement>(type: T["type"]): T[] {
    const elements: T[] = []
 
    this.form.pages.forEach((page) => {
      page.elements.forEach((element) => {
        if (element.type === type) {
          elements.push(element as T)
        }
      })
    })
 
    return elements
  }
 
  // 必須質問の取得
  getRequiredElements(): FormElement[] {
    const required: FormElement[] = []
 
    this.form.pages.forEach((page) => {
      page.elements.forEach((element) => {
        if (element.required) {
          required.push(element)
        }
      })
    })
 
    return required
  }
 
  // 統計情報の取得
  getStatistics(): {
    totalElements: number
    requiredElements: number
    elementTypeCount: Record<string, number>
  } {
    let totalElements = 0
    let requiredElements = 0
    const elementTypeCount: Record<string, number> = {}
 
    this.form.pages.forEach((page) => {
      page.elements.forEach((element) => {
        totalElements++
 
        if (element.required) {
          requiredElements++
        }
 
        elementTypeCount[element.type] = (elementTypeCount[element.type] || 0) + 1
      })
    })
 
    return {
      totalElements,
      requiredElements,
      elementTypeCount,
    }
  }
}

バリデーション関数

import type { FormElement, ShortText, LongText, NumberField, CheckboxField } from "md2form"
 
// 型安全なバリデーション関数
function validateFormElement(element: FormElement): string[] {
  const errors: string[] = []
 
  // 共通バリデーション
  if (element.required && !element.label) {
    errors.push("必須質問にはラベルが必要です")
  }
 
  // 型別バリデーション
  switch (element.type) {
    case "short_text":
    case "long_text":
      const textElement = element as ShortText | LongText
      if (textElement.maxLength && textElement.maxLength <= 0) {
        errors.push("maxLengthは正の値である必要があります")
      }
      break
 
    case "number":
      const numberElement = element as NumberField
      if (
        numberElement.min !== undefined &&
        numberElement.max !== undefined &&
        numberElement.min > numberElement.max
      ) {
        errors.push("最小値は最大値以下である必要があります")
      }
      break
 
    case "checkbox":
      const checkboxElement = element as CheckboxField
      if (checkboxElement.options.length === 0) {
        errors.push("チェックボックスには選択肢が必要です")
      }
      if (
        checkboxElement.maxSelected &&
        checkboxElement.maxSelected > checkboxElement.options.length
      ) {
        errors.push("最大選択数は選択肢数以下である必要があります")
      }
      break
  }
 
  return errors
}

カスタム型の拡張

import type { FormSettings, FormDocument } from "md2form"
 
// カスタム設定型の拡張
interface ExtendedFormSettings extends FormSettings {
  customBranding?: {
    logo?: string
    primaryColor?: string
    secondaryColor?: string
  }
  analytics?: {
    trackingId?: string
    enableHeatmap?: boolean
  }
}
 
// 拡張されたフォーム文書型
interface ExtendedFormDocument extends Omit<FormDocument, "settings"> {
  settings?: ExtendedFormSettings
}
 
// 使用例
function processExtendedForm(form: ExtendedFormDocument) {
  console.log(form.title)
 
  if (form.settings?.customBranding?.logo) {
    console.log("ロゴ:", form.settings.customBranding.logo)
  }
 
  if (form.settings?.analytics?.trackingId) {
    console.log("トラッキングID:", form.settings.analytics.trackingId)
  }
}

型定義の利点

1. 開発時の安全性

import type { ShortText } from "md2form"
 
// コンパイル時にエラーを検出
const element: ShortText = {
  type: "short_text",
  label: "お名前",
  maxLenght: 100, // ❌ タイプミス - コンパイルエラー
}
 
// 正しい記述
const element: ShortText = {
  type: "short_text",
  label: "お名前",
  maxLength: 100, // ✅ 正しい
}

2. IntelliSenseサポート

IDEで自動補完とドキュメントが利用できます。

3. リファクタリングの安全性

型定義により、大規模な変更時も安全にリファクタリングできます。

JSON型定義(補足)

src/types/json.types.tsには、JSON処理に関連する補助型が定義されています。これらもmd2formから直接インポート可能です:

import type { JsonValue, JsonObject, JsonArray } from "md2form"
 
// JSON値の型定義
type JsonValue = string | number | boolean | null | JsonObject | JsonArray
 
interface JsonObject {
  [key: string]: JsonValue
}
 
interface JsonArray extends Array<JsonValue> {}
 
// フォームデータのJSON変換用型
type SerializableFormDocument = Omit<FormDocument, "pages"> & {
  pages: SerializableJsonObject[]
}

型定義のベストプラクティス

1. 明示的な型注釈

import { parseMarkdownToForm } from "md2form"
import type { FormDocument } from "md2form"
 
// ❌ 型推論に依存
const form = await parseMarkdownToForm(markdown)
 
// ✅ 明示的な型指定
const form: FormDocument = await parseMarkdownToForm(markdown)

2. 型ガードの活用

import type { ShortText } from "md2form"
 
// ❌ 型キャストに依存
const shortText = element as ShortText
console.log(shortText.maxLength)
 
// ✅ 型ガードを使用
if (isShortText(element)) {
  console.log(element.maxLength) // 型安全
}

3. ユニオン型の適切な処理

import type { FormElement } from "md2form"
 
// ✅ discriminated unionの活用
function processElement(element: FormElement) {
  switch (element.type) {
    case "short_text":
      // この時点でelementはShortText型
      console.log(element.maxLength)
      break
    case "number":
      // この時点でelementはNumberField型
      console.log(element.min, element.max)
      break
    // ... その他のcase
  }
}

次のステップ

型定義を理解したら、以下のページでさらに詳細な情報を確認してください: