`<wa-button>`にCSSカスタムステートを追加
<wa-button>コンポーネントにdisabled・icon-button・link・loadingの4つのCSSカスタムステートが追加されました。これにより、属性セレクタや内部実装に依存せず、:state()擬似クラスを使ってボタンのスタイリングが行えるようになります。
背景
CSSカスタムステート(Custom State Pseudo-Class)は、カスタム要素が内部状態を外部のCSSに公開するための仕組みです。これまで<wa-button>では、disabled属性やhref属性の有無に依存したセレクタでスタイルを記述する必要がありました。discuss:2185では、こうした状態をより簡潔にスタイリングする手段として@cssstateによるカスタムステートの公開が提案されていました。
すでに<wa-copy-button>では:state(success)と:state(error)が実装されており、今回の変更はその方針を<wa-button>に拡張したものです。コンポーネント間でスタイリングのアプローチが統一されることで、ユーザーは一貫したAPIでスタイルをカスタマイズできるようになります。
技術的な変更
button.tsに状態の同期ロジックが追加され、各プロパティの変更時にcustomStatesへ値が反映されるようになりました。
disabledとloadingについては、既存の@watchデコレータのハンドラにcustomStates.set()の呼び出しを追加しています。
@watch('disabled', { waitUntilFirstUpdate: true })
handleDisabledChange() {
this.customStates.set('disabled', this.disabled);
this.updateValidity();
}
@watch('loading', { waitUntilFirstUpdate: true })
handleLoadingChange() {
this.customStates.set('loading', this.loading);
}
linkステートは新たに追加されたhandleHrefChange()ハンドラで管理されます。this.isLink()の戻り値を使うことで、hrefの有無に基づくリンクモードへの切り替えを正確にステートに反映します。
@watch('href')
handleHrefChange() {
this.customStates.set('link', this.isLink());
}
icon-buttonステートはスロットの内容変化を検出する既存のロジック内で更新されます。アイコンのみでテキストや他の要素を含まない場合にtrueとなるthis.isIconButtonの値をそのまま使用しています。
// It's only an icon button if there's an icon and nothing else
this.isIconButton = hasIcon && !hasText && !hasOtherElements;
this.customStates.set('icon-button', this.isIconButton);
これらの変更により、ユーザーは以下のようなセレクタでスタイルを記述できます。
wa-button:state(loading) {
opacity: 0.7;
}
wa-button:state(icon-button) {
border-radius: 50%;
}
設計判断
今回の変更では、新しい状態管理の仕組みを導入せず、既存の@watchパターンにcustomStates.set()の呼び出しを追加する方式が採用されました。
各ステートの更新は、それぞれ対応するプロパティの変更ハンドラに委ねられており、状態の同期ポイントが分散しています。一方で、この設計はそれぞれのハンドラが単一の責務を持つという既存のアーキテクチャとの一貫性を保っています。icon-buttonのようにプロパティではなくスロット内容に依存する状態も、既存のスロット変化検出ロジックの中で自然に扱われています。
また、JSDocコメントに@cssstateタグでステートが文書化されており、ツールによる自動検出やドキュメント生成への対応も意識された変更となっています。
まとめ
今回の変更は、既存の内部状態管理の仕組みを活かしながら、最小限の追加コードでCSSカスタムステートを公開するものです。<wa-copy-button>で確立したパターンを<wa-button>に適用することで、コンポーネントライブラリ全体でのスタイリングAPIの一貫性が高まります。