`wa-dropdown-item` の無効状態でもクリックイベントが発火するバグを修正
wa-dropdown-item が disabled 属性を持つ場合でも、プログラム的な .click() 呼び出しによってクリックイベントが発火してしまうバグが修正されました。
背景
wa-dropdown-item のクリックイベント抑制は、これまで Shadow DOM 内部のハンドラ(handleClick)のみで実装されていました。しかし、Shadow DOM の内側で発火したイベントを止めても、ホスト要素に対してプログラム的に .click() を呼び出すと、ホスト要素のイベントリスナーへ直接イベントが届くため抑制できないという盲点がありました。#1817 で報告されたこの問題では、disabled な wa-dropdown-item をクリックしても wa-select イベントが誤って発火することが確認されています。
技術的な変更
ホスト要素に新たなイベントハンドラ handleHostClick を追加し、disabled 状態でのクリックイベントを遮断するようになりました。
追加されたハンドラは以下の通りです:
/** Prevents click events from firing on the host when the item is disabled (e.g. programmatic .click() calls). */
private handleHostClick = (event: MouseEvent) => {
if (this.disabled) {
event.preventDefault();
event.stopImmediatePropagation();
}
};
このハンドラは connectedCallback でホスト要素自身に登録され、disconnectedCallback で解除されます:
connectedCallback() {
super.connectedCallback();
this.addEventListener('click', this.handleHostClick);
// ...既存のリスナー
}
disconnectedCallback() {
super.disconnectedCallback();
this.removeEventListener('click', this.handleHostClick);
// ...既存のリスナー
}
event.stopImmediatePropagation() を用いることで、同一要素に登録された後続のリスナーへの伝播も含めて完全に遮断します。これにより、ユーザー操作によるクリックだけでなく、el.click() のようなプログラム的呼び出しも確実に無効化されます。
テストも追加されており、disabled な要素に対して .click() を呼び出してもイベントハンドラが呼ばれないことを検証しています:
it('should not fire click event when disabled and .click() is called programmatically', async () => {
const el = await fixture<WaDropdownItem>(html` <wa-dropdown-item disabled>Item</wa-dropdown-item> `);
await el.updateComplete;
const clickHandler = sinon.spy();
el.addEventListener('click', clickHandler);
el.click();
expect(clickHandler).not.to.have.been.called;
});
設計判断
Shadow DOM 内部のハンドラとホスト要素のハンドラを並列に持つ構成が採用されました。
既存の handleClick(Shadow DOM 内部)はそのままに、新たな handleHostClick(ホスト要素)を追加する設計です。Shadow DOM を経由しないプログラム的な .click() 呼び出しは、Shadow DOM 内部のイベントキャプチャでは阻止できないため、ホスト要素側での防御が必要になります。stopImmediatePropagation を選択しているのは、stopPropagation では同一要素の後続リスナーに届いてしまうためです。また、ライフサイクルメソッドで対称的に登録・解除することで、メモリリークを防ぐ丁寧な実装になっています。
まとめ
Webコンポーネントでは Shadow DOM の内外でイベント伝播の経路が異なり、内部だけでのガードでは不十分なケースがあります。本修正はホスト要素での防御レイヤーを追加することで、プログラム的なクリック操作を含むすべての経路を確実にカバーし、disabled の語義通りの動作を保証します。