`<wa-select>`の`with-clear`使用時に`selected`属性が無視される不具合を修正
<wa-select>にwith-clear属性が指定されている場合、子要素の<wa-option>のselected属性が無視される不具合が修正されました。これにより、Vue.jsなどのフレームワークでプロパティバインディングを使用した際の選択状態が正しく反映されるようになります。
背景
with-clear属性付きの<wa-select>で、<wa-option>にselected属性を指定してもその選択状態が反映されず、セレクトボックスが空のまま表示される問題が報告されていました。#1922によると、placeholder属性と併用した場合でも同様の現象が発生していました。
<wa-select placeholder="Placeholder" with-clear>
<wa-option value="option-1" selected>Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
</wa-select>
上記のコードでは「Option 1」が選択されるべきですが、実際にはプレースホルダーが表示されたままとなっていました。この問題は特にVue.jsの:selected構文を使用した際に顕著でした。
根本原因
不具合の原因は<wa-option>のwillUpdateライフサイクルメソッドにありました。初回レンダリング時にselected = defaultSelectedが無条件に実行され、フレームワークが直接設定したselectedプロパティの値を上書きしていました。
修正前のコード:
if (changedProperties.has('defaultSelected')) {
if (!this.closest<WaSelect>('wa-combobox, wa-select')?.hasInteracted) {
const oldVal = this.selected;
this.selected = this.defaultSelected;
this.requestUpdate('selected', oldVal);
}
}
defaultSelectedはデフォルトでfalseであるため、Vue.jsが:selected="true"でプロパティを設定しても、このコードによってfalseに戻されていました。hasInteractedがfalseの状態(ユーザーがまだ操作していない状態)では、常にこの上書きが発生します。
技術的な変更
修正は2つのライフサイクルメソッドに対して行われました。
willUpdateメソッドの修正
defaultSelectedがtrueの場合のみselectedを同期するように条件を追加しました。
if (changedProperties.has('defaultSelected')) {
if (!this.closest<WaSelect>('wa-combobox, wa-select')?.hasInteracted) {
// Only sync if defaultSelected is becoming true
if (this.defaultSelected) {
const oldVal = this.selected;
this.selected = this.defaultSelected;
this.requestUpdate('selected', oldVal);
}
}
}
この変更により、defaultSelectedがfalseの場合にselectedプロパティが上書きされることがなくなります。フレームワークが直接selectedを設定した値は保持されます。
firstUpdatedメソッドの追加
初回レンダリング完了時に、selectedがtrueでdefaultSelectedがfalseの場合に親の<wa-select>に選択変更を通知する処理を追加しました。
protected firstUpdated(changedProperties: PropertyValues<this>) {
super.firstUpdated(changedProperties);
if (this.selected && !this.defaultSelected) {
const parent = this.closest<WaSelect>('wa-select, wa-combobox');
if (parent && !parent.hasInteracted) {
parent.selectionChanged?.();
}
}
}
この処理により、Vue.jsの:selectedバインディングなど、プロパティとして直接設定された選択状態が親コンポーネントに正しく伝播します。
テストの追加
修正を検証するため、select.test.tsに3つのテストケースが追加されました。
-
with-clear単体での動作確認:selected属性が正しく反映されることを検証 -
with-clearとplaceholderの併用: #1922で報告された組み合わせでの動作を検証 -
複数選択モードでの動作確認:
multiple属性との併用時に複数のselectedが正しく反映されることを検証
it('should select options with selected attribute, with-clear, and placeholder', async () => {
const el = await fixture<WaSelect>(html`
<wa-select placeholder="Placeholder" with-clear>
<wa-option value="option-1" selected>Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
</wa-select>
`);
expect(el.value).to.equal('option-1');
expect(el.displayInput.value).to.equal('Option 1');
});
すべてのテストは成功しており、既存のセレクトコンポーネントの動作に影響がないことが確認されています。
設計判断
selectedとdefaultSelectedの使い分けを明確にする方向で修正が行われました。
defaultSelectedはHTML属性からの初期値を表し、selectedは現在の選択状態を表すプロパティです。フレームワークがプロパティバインディングでselectedを直接設定する場合、それはdefaultSelectedとは独立した値として扱われるべきです。
PRの説明によると、この修正は<wa-option>単体での動作に限定されており、<wa-input>など他のwith-clearを使用するコンポーネントには影響しません。これらのコンポーネントは子要素のselected属性を使用しない設計のためです。
まとめ
本PRは、ライフサイクルメソッドの条件分岐を精緻化することで、HTML属性ベースの初期化とフレームワークのプロパティバインディングを両立させる修正です。defaultSelectedがtrueの場合のみselectedを同期し、それ以外の場合はフレームワークが設定した値を尊重する設計により、宣言的UIフレームワークとの互換性が向上しています。