`<wa-select>` の空値オプション選択時にラベル重複が起きるバグを修正
<wa-select> で空文字列("")をバリューに持つオプションを選択した際、ラベルが入力内のテキストと重なる表示バグが修正されました。:state(blank) カスタムステートの判定ロジックを displayLabel も考慮する形に拡張したことで、バリューと表示テキストが独立して扱われるようになっています。
背景
<wa-select> は、ラベルを「入力の内側」に配置するかどうかの判定を value の有無のみで行っていました。しかし value="" かつ <wa-option> にテキストコンテンツがある場合(例:「全従業員」のような初期選択肢で空文字列をバリューとして使うケース)、バリューが空でも表示テキストが存在するため、ラベルと選択テキストが同一領域に重なって描画されるという問題が発生していました。
これは Issue #1920 として報告されており、サーバーサイドのフィルタリング機能で「フィルターなし」を意味する空文字列バリューを使用しているケースが具体的なユースケースとして示されています。空文字列バリューを避けるワークアラウンド("all" などの非空文字列に置き換える)は存在しましたが、バックエンド側のデータモデルとの乖離を生じさせるものでした。
技術的な変更
変更の中心は select.ts における :state(blank) カスタムステートの判定条件の拡張と、クリアアイコンの表示条件の修正、そして handleValueChange でのリセット処理です。
:state(blank) の判定条件を拡張:
変更前は value のみを参照していたため、空文字列バリューのオプションが選択された状態でも blank ステートが付与されていました。
変更前:
if (changedProperties.has('value')) {
this.customStates.set('blank', !this.value);
}
変更後:
if (changedProperties.has('value') || changedProperties.has('displayLabel')) {
this.customStates.set('blank', !this.value && !this.displayLabel);
}
changedProperties の監視対象に displayLabel を追加し、blank の判定を「value も displayLabel も空である」という複合条件に変更しています。これにより、空文字列バリューのオプションが選択されていても displayLabel にテキストが入っていれば blank ステートは付与されず、ラベルが入力内部に折り畳まれたままになります。
クリアアイコンの表示条件を修正:
withClear 属性が指定された場合のクリアアイコン表示条件も同様に修正されています。
変更前:
const hasClearIcon =
(this.hasUpdated || isServer) && this.withClear && !this.disabled && this.value && this.value.length > 0;
変更後:
const hasClearIcon =
(this.hasUpdated || isServer) &&
this.withClear &&
!this.disabled &&
(this.displayLabel || (this.value && this.value.length > 0));
value が空文字列でも displayLabel が存在する場合にクリアアイコンを表示するよう拡張されており、ユーザーが空バリューの選択を解除できるようになっています。
値変更ハンドラーでの displayLabel リセット:
if (this.value !== null) {
this.displayLabel = ''; // 追加
this.selectionOrder.clear();
this.setSelectedOptions([]);
this.displayInput.focus({ preventScroll: true });
}
handleValueChange の冒頭で displayLabel を明示的にクリアすることで、値の変更時に前の表示ラベルが残存しないよう保証しています。
設計判断
value と displayLabel を独立した状態として扱う という方針が採られています。Web Awesomeの <wa-select> では、フォームの送信値(value)と表示テキスト(displayLabel)は概念的に異なるものです。空文字列は有効なフォーム値であり、その場合でも「何かが選択されている」状態を正確に反映するためには displayLabel を独立した信号として扱う必要があります。
:state(blank) カスタムステートはCSSのスタイリングに使われるため、このステートが誤って付与されると視覚表現(ラベルの浮動・ テキストの重なり)に直接影響します。value 単体ではなく「表示する内容が何もない」という意味論的な状態を正確に表現するための修正といえます。
まとめ
本PRは、value と displayLabel を独立した状態として扱うことで、フォームの送信値と表示テキストの乖離が起きうるユースケースに対応した修正です。空文字列を有効なフォーム値として使いながらも、ラベルの表示位置やクリアアイコンの可視性が正しく制御されるようになり、バックエンドのデータモデルに合わせたUI実装が可能になります。