`wa-option`のdisabled状態をCSS Custom Stateで管理する修正
wa-optionのdisabledプロパティをJavaScriptから設定しても見た目が変わらないバグを、CSS Custom State(:state(disabled))の導入によって修正しました。属性の反映に依存したスタイル定義から、より適切なカスタム状態ベースの管理へと移行しています。
背景
disabled属性とdisabledプロパティの動作が一致しないという問題が#1997で報告されていました。
<wa-option>では、HTMLのdisabled属性はDOMに反映(reflect)されない設計になっていました。しかしCSSのスタイル定義では:host([disabled])という属性セレクタを使用していたため、JavaScriptでoption.disabled = trueとプロパティを設定しても属性がDOMに追加されず、スタイルが適用されませんでした。その結果、ARIAのaria-disabledは正しく設定されてクリックも無効化されるにもかかわらず、グレーアウトやcursor: not-allowedが表示されないという不整合な状態が生じていました。
根本的な原因は、disabled属性のreflectなしという設計に対して、スタイルが属性の存在に依存していた点にあります。
技術的な変更
スタイル定義をDOM属性セレクタからCSS Custom Stateセレクタへ置き換え、コンポーネント側でカスタム状態を同期するコードを追加しています。
option.styles.tsでは、すべての:host([disabled])セレクタが:host(:state(disabled))に変更されました。
変更前:
:host(:not([disabled], :state(current)):is(:state(hover), :hover)) {
background-color: var(--wa-color-neutral-fill-normal);
color: var(--wa-color-neutral-on-normal);
}
:host([disabled]:state(current)) {
background-color: var(--wa-color-brand-fill-loud);
color: var(--wa-color-brand-on-loud);
opacity: 1;
}
:host([disabled]) {
outline: none;
opacity: 0.5;
cursor: not-allowed;
}
変更後:
:host(:not(:state(disabled), :state(current)):is(:state(hover), :hover)) {
background-color: var(--wa-color-neutral-fill-normal);
color: var(--wa-color-neutral-on-normal);
}
:host(:state(disabled):state(current)) {
background-color: var(--wa-color-brand-fill-loud);
color: var(--wa-color-brand-on-loud);
opacity: 1;
}
:host(:state(disabled)) {
outline: none;
opacity: 0.5;
cursor: not-allowed;
}
option.tsでは、disabledプロパティの変更を検知するchangedPropertiesハンドラに、customStates.set による状態の同期処理を1行追加しています。
追加コード:
if (changedProperties.has('disabled')) {
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
this.customStates.set('disabled', this.disabled); // 追加
}
JSDocにも@cssstate disabledのアノテーションが追加され、コンポーネントのパブリックAPIとして明示されるようになりました。
設計判断
属性reflectの追加ではなく、CSS Custom Stateの導入という修正方針が選択されました。
PRの説明では「disabled属性はreflectされないが、スタイルがそれに依存していた。より適切な修正はdisabled用のカスタム状態を使うことだ」と明言されています。disabled属性のreflectを有効にする方法もありますが、この選択肢は採用されていません。Custom Stateはコンポーネント内部の状態をCSSに公開するために設計された仕組みであり、DOM属性よりも意味的に正確な手段です。すでにcurrent・selected・hoverがCustom Stateとして実装されていたことからも、disabledを同じ管理方式に揃えることはコンポーネント設計の一貫性を保つうえで自然な判断といえます。
まとめ
DOM属性へのreflectに依存せずCustom Stateで状態を管理するアプローチにより、disabledプロパティと属性の見た目が統一されるとともに、スタイルとコンポーネント状態の結合をより明確に分離できました。<wa-button>でも同様にdisabledカスタム状態が導入されていることからも、このパターンがWebAwesomeコンポーネントライブラリ全体の標準的な設計として定着しつつあることがわかります。