Shadow DOM境界を超えたPopoverのクリック検出を修正
Web ComponentsのShadow DOM内で wa-popover を使用すると、ポップオーバー内の要素をクリックした際に意図せず閉じてしまう問題が修正されました。この変更により、Shadow DOM境界を越えたイベント伝播を正しく追跡できるようになります。
背景
wa-popover コンポーネントは、ユーザーがポップオーバーの外側をクリックしたときに閉じる機能を持っています。しかし、このコンポーネントを別のカスタム要素のShadow DOM内に配置すると、ポップオーバー内部の textarea や button などの要素をクリックしただけで、ポップオーバーが即座に閉じてしまう問題が報告されていました。#1969 がこの問題を報告しています。
問題の原因は handleDocumentClick() メソッドにありました。このメソッドは Element.closest() を使用してクリックイベントのターゲットが自身の要素内かどうかを判定していましたが、closest() はShadow DOM境界を越えて要素を探索できないため、ネストされたShadow DOM内では常に null を返していました。
この制約により、Shadow DOM内に配置されたポップオーバーは、内部要素のクリックを「外側のクリック」と誤認識し、意図せず閉じる動作をしていました。
技術的な変更
packages/webawesome/src/components/popover/popover.ts の handleDocumentClick() メソッドが、closest() から composedPath() を使用した実装に変更されました。
変更前:
private handleDocumentClick = (event: PointerEvent) => {
const target = event.target as HTMLElement;
// Ignore clicks on the anchor so it will be closed by the anchor's click handler
if (this.anchor && event.composedPath().includes(this.anchor)) {
return;
}
// Detect when clicks occur outside the popover
if (target.closest('wa-popover') !== this) {
this.open = false;
}
};
変更後:
private handleDocumentClick = (event: PointerEvent) => {
// Ignore clicks on the anchor so it will be closed by the anchor's click handler
if (this.anchor && event.composedPath().includes(this.anchor)) {
return;
}
// Detect when clicks occur outside the popover (using composedPath to traverse shadow DOM boundaries)
if (!event.composedPath().includes(this)) {
this.open = false;
}
};
event.composedPath() はイベントが伝播した全ての要素を配列で返すメソッドで、Shadow DOM境界を越えて要素を追跡できます。includes(this) による判定により、クリックイベントのパスに自身が含まれていない場合のみポップオーバーを閉じるようになりました。
不要になった target 変数の宣言も削除され、コードが簡潔になっています。この変更により、ポップオーバー内部のどの要素がクリックされても、そのイベントパスには必ずポップオーバー自身が含まれるため、誤って閉じることがなくなります。
設計判断
既存のアンカー要素チェックと同じパターンを採用する方式 が選択されました。
PRの説明によると、同じメソッド内で既にアンカー要素の判定に event.composedPath().includes(this.anchor) が使用されていました。今回の修正は、この既存パターンをポップオーバー自身の判定にも適用したものです。
このアプローチは、closest() の制約を回避しつつ、ファイル内の既存コードと一貫性を保つための選択です。同一のメソッド内でイベント追跡の方法を統一することで、コードの保守性が向上しています。
まとめ
本PRは、Shadow DOM環境下でのポップオーバーの誤動作を修正した変更です。closest() から composedPath() への変更により、Shadow DOM境界を越えたイベント追跡が可能になり、ネストされたWeb Components内でもポップオーバーが正しく動作するようになりました。この修正は、既存のアンカー判定ロジックとの一貫性を保ちながら、Shadow DOMのカプセル化を尊重した実装といえます。