`<wa-input>` / `<wa-number-input>` の無効値をネイティブ入力と一致するよう空文字列にサニタイズ

shoelace-style/webawesome

type="number"type="date" など特定の型を持つ <wa-input> および <wa-number-input> に無効な値を設定した際、ネイティブの <input> と同様に空文字列へ正規化されるようになりました。

背景

#2323 で報告されたこのバグは、<wa-input type="number"> に対して "abc" などの非数値文字列を設定すると、カスタム要素の value プロパティがそのまま "abc" を返し続けるという問題でした。ネイティブの <input type="number"> では同じ操作を行うと、ブラウザが値をサニタイズして "" を返すのが正しい動作です。

影響を受けていた型は以下の4種類です:

  • number
  • date
  • time
  • datetime-local

これらはいずれも、ブラウザが仕様に基づいて無効な入力を自動的に空文字列へ正規化する型です。カスタム要素がこの挙動を再現できていなかったため、value プロパティを経由するコードとネイティブ入力を想定したコードで動作が乖離していました。

技術的な変更

修正は updated() ライフサイクルフック内に「ネイティブ入力の値を参照して内部状態を同期する」ロジックを追加することで実現しています。

input.ts の変更前:

updated(changedProperties: PropertyValues<this>) {
  super.updated(changedProperties);

  if (changedProperties.has('value') || changedProperties.has('defaultValue')) {
    this.customStates.set('blank', !this.value);
    this.updateValidity();
  }
}

変更後:

updated(changedProperties: PropertyValues<this>) {
  super.updated(changedProperties);

  if (changedProperties.has('value') || changedProperties.has('defaultValue') || changedProperties.has('type')) {
    // Types where the browser sanitizes invalid input to an empty string. Mirror that behavior so `value` stays
    // consistent with the native input (e.g. setting "abc" on type="number" resolves to "").
    const sanitizingTypes = ['number', 'date', 'time', 'datetime-local'];
    if (this.input && sanitizingTypes.includes(this.type) && this.value && this.input.value !== this.value) {
      this._value = this.input.value;
    }

    this.customStates.set('blank', !this.value);
    this.updateValidity();
  }
}

仕組みはシンプルです。Lit の updated() が呼ばれた時点では、Shadow DOM 内のネイティブ <input> にはすでにプロパティ経由で値が渡されており、ブラウザによるサニタイズも完了しています。そこで this.input.value(ブラウザが正規化した後の値)と this.value(カスタム要素が保持している値)を比較し、差異があれば内部フィールド this._value をネイティブ側の値で上書きします。this.value ではなく this._value を直接書き換えることで、Lit のリアクティブな再レンダリングループに入らずに値を修正できます。

<wa-number-input> にも同様のロジックが追加されています。こちらは常に数値型として扱われるため sanitizingTypes による型チェックは不要で、this.input.value !== this.value の比較のみで十分です。また、この修正に合わせて changedProperties.has('defaultValue') のチェックも追加され、HTML 属性経由で無効な初期値が指定された場合も同じサニタイズが適用されるようになりました。

設計判断

ネイティブ <input> の動作をソースオブトルースとして参照する設計が採用されました。

サニタイズロジックをカスタム要素側に独自実装する(たとえば isNaN() チェックで数値を検証するなど)のではなく、ブラウザがすでに正規化した結果を読み取るだけにとどめています。これにより、仕様に準拠したサニタイズ処理をブラウザに委譲でき、型ごとのエッジケース(数値の精度、日付フォーマットのロケール差異など)をカスタム要素が個別に考慮する必要がなくなります。

type プロパティの変更も監視対象に加えている点も注目に値します。たとえば type="text" から type="number" に動的に切り替えた際、それまで保持していた非数値の value が自動的にサニタイズされるようになります。この対称性はネイティブの <input> の挙動とも一致しています。

まとめ

この修正は、数行の条件チェックを追加するだけで「カスタム要素の値プロパティがネイティブ入力と一致する」という基本的な契約を保証したものです。サニタイズ処理をブラウザに委譲するアプローチにより、将来的に型が追加された場合でも sanitizingTypes 配列に型名を追加するだけで対応できる拡張性を確保しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
c8a824e2

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)」という構成が明確で、非常に理解しやすい構造になっています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```typescript:path/to/file.ts)やGitHubのPR/Issueへのリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

LitのライフサイクルフックやShadow DOMといった概念を前提としており、専門知識を持つエンジニアという対象読者に適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクション、各パラグラフが「総論→各論」の構造で書かれ、トピックセンテンスが先頭にあり、非常に読みやすいです。段落の長さも適切です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているinput.tsのコードブロック(変更前・変更後)は、提供されたDiff情報と正確に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「ライフサイクルフック」「サニタイズ」「ソースオブトルース」などの技術用語が、文脈に沿って正確かつ効果的に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「_valueを直接書き換えることで再レンダリングループを回避する」というLitの挙動に関する説明など、技術的に正確で深い洞察に基づいた解説がなされています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(影響範囲、修正内容、設計思想など)は、PRのタイトル、Diff、コードコメントから裏付け可能であり、ハルシネーションは一切見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#2324)やIssue番号(#2323)などの数値・固有名詞はすべて正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルは、PRの技術的なタイトル「Sync form value against native inputs」を、より具体的で分かりやすい表現に落とし込んでおり、内容と完全に一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事の内容は提供されたPR情報に限定されており、サポート状況やリリース予定といったPR外の知識を持ち込むことなく、事実に基づいた記述に徹しています。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「〜ようになりました」という表現が、このPRによって変更が適用されたという事実を正確に伝えており、時間表現の歪曲はありません。