`<wa-select>` の `change`/`input` イベントタイミングバグを修正

shoelace-style/webawesome

Web Awesome 3.4 で導入されたバグにより、<wa-select><wa-combobox><wa-option>change および input イベントが誤ったタイミングで発火していた問題を修正しました。イベントハンドラ内で event.target.value を参照した際に、選択後の正しい値が取得できなかった不具合が解消されます。

背景

change / input イベントのタイミングがずれると、イベントハンドラ内で event.target.value を読み取っても選択前の古い値を返すという実用上の問題が生じます。この問題はDiscordコミュニティで報告され、<wa-select> に加えて関連するProコンポーネントリポジトリにも影響することが確認されていました。

原因は2つのコンポーネントにまたがる実装にありました。<wa-option>selected プロパティが変更された際に handleDefaultSlotChange() を呼び出していたこと、および <wa-select>optionValues キャッシュ構築ロジックが valuenull の場合に空の Set を返していたことが、イベント発火タイミングのずれを引き起こしていました。

技術的な変更

修正は option.tsselect.ts の2ファイルにわたる最小限の変更で行われました。

option.ts の変更: selected プロパティの変更時に呼び出されていた handleDefaultSlotChange() の呼び出しを削除しました。

変更前:

if (changedProperties.has('selected')) {
  this.setAttribute('aria-selected', this.selected ? 'true' : 'false');
  this.customStates.set('selected', this.selected);
  this.handleDefaultSlotChange();
}

変更後:

if (changedProperties.has('selected')) {
  this.setAttribute('aria-selected', this.selected ? 'true' : 'false');
  this.customStates.set('selected', this.selected);
}

selected の変更のたびにスロット変更処理が再実行されることで、<wa-select> 側の値更新と change イベント発火の順序が乱れていたと考えられます。

select.ts の変更: optionValues キャッシュの構築において、valuenull の場合に空の Set を生成していた分岐を削除し、常にDOM上の <wa-option> 要素から値を収集するよう統一しました。

変更前:

if (this.optionValues === undefined) {
  if (value == null) {
    this.optionValues = new Set(null);
  } else {
    this.optionValues = new Set(
      this.getAllOptions()
        .filter(option => !option.disabled)
        .map(option => option.value),
    );
  }
}

変更後:

if (this.optionValues === undefined) {
  this.optionValues = new Set(
    this.getAllOptions()
      .filter(option => !option.disabled)
      .map(option => option.value),
  );
}

初期値なしの状態(value == null)でも optionValues が正しく構築されることで、選択操作後の値の更新とイベント発火が同期するようになります。

テストの追加

リグレッション防止のため、select.test.ts に2つのテストケースが追加されました。どちらも change / input イベントハンドラ内での event.target.value の値を検証することで、タイミングのずれを直接検出できるようになっています。

1つ目は初期値ありの状態(value="option-1")から別のオプションを選択した際に event.target.value が新しい値を返すことを確認します。2つ目は初期値なしの状態から選択した際のケースを対象としており、今回の select.ts の修正が必要になったシナリオに対応しています。

el.addEventListener('change', (event: Event) => {
  valueAtChangeTime = (event.target as WaSelect).value;
});
// ...
expect(valueAtChangeTime).to.equal('option-2');

設計判断

selected 変更時の handleDefaultSlotChange() 呼び出しの削除という判断は、副作用の排除を優先したものです。selected はオプションの状態を表すプロパティであり、その変更がスロットの再評価を引き起こすのは責務の観点から適切ではありません。この呼び出しを除去することで、<wa-option> の更新サイクルが <wa-select> の値確定・イベント発火と競合する経路を断ち切っています。

select.tsnull チェック削除については、value == null の場合に空の Set を返すことで「利用可能なオプションが存在しない」という誤ったキャッシュが生成され、後続の値検証ロジックに影響を与えていたと推測できます。DOM から常にオプションを収集する単一パスに統一することで、初期値の有無に関わらず一貫した動作を保証しています。

まとめ

今回の修正は、<wa-option>selected 変更時の不要なスロット再評価と <wa-select> のキャッシュ構築における null 値の特殊扱いという2つの根本原因を除去することで、イベントタイミングの一貫性を回復しています。変更行数は最小限でありながら、テストケースの追加によって同種のリグレッションを将来的に検出できる体制も整備されました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
8da2a6cd

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

ファイル名付きシンタックスハイライトやPR番号のリンク記法など、カスタムMarkdown構文が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Webコンポーネントの内部実装に関する専門的な内容であり、対象読者であるエンジニアに適した技術レベルと表現で書かれています。

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

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

各セクション・各パラグラフが「総論→各論」の構成になっており、トピックセンテンスが明確です。1段落1トピックの原則も守られており、可読性が高いです。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロックは、提供されたDiffの内容と完全に一致しており、ファイル名も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

イベントハンドラ、キャッシュ、スロットなど、Webコンポーネント開発に関する技術用語が文脈に応じて正確に使用されています。

説明の技術的正確性 ✓ PASS

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

イベント発火タイミングのバグ原因と修正内容に関する説明は、Diffのコード変更と論理的に整合しており、技術的に正確です。

事実の突合 ⚠ WARNING

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

記事の主張の大部分はPR情報で裏付けられていますが、「設計判断」セクションにはPRに明記されていないコード変更の意図に関する推測が含まれています。

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

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

PR番号(#2243)やバージョン番号(3.4)など、記事に含まれる数値や固有名詞はすべて正確です。

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

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

記事のタイトルはPRの主題である「Select event fix」をより具体的に説明しており、内容と完全に一致しています。

外部知識の正確性 ✓ PASS

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

PR情報に記載のないバージョンサポート状況やリリース日程などの外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

「3.4で導入された」「修正しました」など、過去の事象に対する時間表現がPR情報と一致しており正確です。