モバイルでステッパーを操作しても仮想キーボードが開かないように修正

shoelace-style/webawesome

<wa-number-input> のステッパーボタンをタッチ操作した際に仮想キーボードが表示される問題を修正しました。pointerType を判別してタッチ時のフォーカス処理を抑制するアプローチにより、マウス操作との一貫した挙動を維持しています。

背景

モバイルデバイスで <wa-number-input> のインクリメント・デクリメントボタンをタップすると、数値入力フィールドにフォーカスが移動し、仮想キーボードが表示されてページがスクロールされるという問題がありました。ディスカッション #2033 でこの問題が報告されています。

ネイティブの <input type="number"> はステッパーボタン操作後にフォーカスを移動する仕様ですが、モバイルではこの挙動がかえってUXを損なっていました。マウスとタッチの入力デバイスを区別して処理を分岐することで、それぞれの文脈に合ったふるまいを実現しています。

技術的な変更

イベントリスナーを click から pointerup に切り替え、PointerEvent.pointerType を参照することでタッチ操作とマウス操作を判別するように変更されました。

変更前:

private handleStepperClick(direction: 'up' | 'down') {
  // ...
  this.input.focus();
}

private maintainFocusOnPointerDown(event: PointerEvent) {
  event.preventDefault();
  this.input.focus();
}

変更後:

private handleStepperPointerUp(direction: 'up' | 'down', event: PointerEvent) {
  // ...
  // Avoid focusing the input on touch to prevent the virtual keyboard from showing
  if (event.pointerType !== 'touch') {
    this.input.focus();
  }
}

private handleStepperPointerDown(event: PointerEvent) {
  // Avoid focusing the input on touch to prevent the virtual keyboard from showing
  if (event.pointerType === 'touch') return;

  event.preventDefault();
  this.input.focus();
}

テンプレート側では、@click@pointerup に置き換え、イベントオブジェクトをハンドラに渡すよう変更されています。

変更前:

@pointerdown=${this.maintainFocusOnPointerDown}
@click=${() => this.handleStepperClick('down')}

変更後:

@pointerdown=${this.handleStepperPointerDown}
@pointerup=${(event: PointerEvent) => this.handleStepperPointerUp('down', event)}

テストコードも同様に incrementButton.click() / decrementButton.click() の呼び出しを dispatchEvent(new PointerEvent('pointerup', { bubbles: true })) に置き換えており、実際のイベントフローと整合するようになっています。

設計判断

click イベントではなく pointerup を採用した点 が、この修正の核心的な設計判断です。

click イベントはポインターデバイスの種別情報を持たないため、タッチとマウスを区別できません。一方、PointerEventpointerType プロパティに 'touch''mouse''pen' のいずれかを持ち、入力デバイスの判別が可能です。pointerup を採用することで、既存の pointerdown ハンドラと統一されたイベントモデルに整理されています。

また、pointerdownpointerup の両方でタッチ判定を行っている点も注目されます。pointerdown でタッチ時に event.preventDefault() をスキップすることで、ブラウザのデフォルト動作によるフォーカス移動を防ぎ、pointerup でも同様にフォーカス処理を省略することで二重の防護を実現しています。

まとめ

PointerEvent.pointerType を活用することで、タッチとマウスそれぞれに最適なフォーカス挙動を単一のコンポーネントで実現しました。イベントリスナーを click から pointerup に統一したことで、入力デバイスの文脈を意識したインタラクション設計のパターンとして参考になる変更です。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
774dd8ba

この記事は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リンク記法の正確性

ファイル名付きのシンタックスハイライト、GitHubのディスカッションへのリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

PointerEventやイベントハンドリングに関する知識を前提としており、専門知識を持つエンジニアという対象読者に適合した内容です。

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

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

各セクション・各パラグラフがトピックセンテンスで始まっており、構造が非常に明快です。1段落1トピックの原則も守られています。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロック(変更前・変更後)は、提供されたDiff情報と完全に一致しています。テストコードの変更に関する言及も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「PointerEvent」「pointerType」「仮想キーボード」などの技術用語が正確かつ適切な文脈で使用されています。

説明の技術的正確性 ✓ PASS

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

「click」イベントと「PointerEvent」の違いや、`pointerType`を用いた条件分岐のロジックに関する説明は、技術的に正確で論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのタイトル、説明、Diff内容によって裏付けられています。「設計判断」セクションはコードの意図を正確に解説しており、ハルシネーションは見られません。

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

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

PR番号「#2202」および関連するディスカッション番号「#2033」が正確に記載されています。

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

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

記事のタイトルはPRのタイトル(Don't focus on number input when tapping steppers on mobile)の内容を正確に反映しています。

外部知識の正確性 ✓ PASS

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

PR情報に含まれないバージョン情報やリリース予定などの外部知識は記載されていません。

時間表現の正確性 ✓ PASS

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

完了した変更について過去形で記述されており、時間表現に誤りはありません。