PopoverのDOM移動時にイベントハンドラを維持
Web AwesomeのPopoverコンポーネントが appendChild などでDOM内を移動すると、AbortController のシグナルが中断されてイベントリスナーが失われる問題が修正されました。この変更により、要素が再接続された際にイベントハンドラが適切に再確立されるようになります。
背景
Popoverコンポーネントの親コンテナをDOM内で移動すると、アンカー要素のトリガーイベントや閉じる動作が機能しなくなる問題が報告されていました。#1963 の報告では、以下のような状況で問題が発生していました:
- Popoverの親要素を
appendChildで別の場所に移動 - 移動後、アンカー要素をクリックしてもPopoverが開かない
- 既に開いているPopoverを閉じる操作も機能しない
この問題は、disconnectedCallback 内で AbortController のシグナルが中断されることが原因でした。Web ComponentsのライフサイクルではDOM移動時に disconnectedCallback と connectedCallback が順次呼び出されますが、中断されたシグナルに紐付いたイベントリスナーはそのまま失われていました。
技術的な変更
packages/webawesome/src/components/popover/popover.ts の connectedCallback メソッドに、2つの復旧処理が追加されました。
変更後:
connectedCallback() {
super.connectedCallback();
if (!this.id) {
this.id = uniqueId('wa-popover-');
}
// Recreate event controller if it was aborted
if (this.eventController.signal.aborted) {
this.eventController = new AbortController();
}
// Re-establish anchor connection after being moved in the DOM
if (this.for && this.anchor) {
this.anchor = null; // force reattach
this.handleForChange();
}
}
最初の処理は イベントコントローラの再作成 です。eventController.signal.aborted をチェックし、中断されていれば新しい AbortController インスタンスを生成します。これにより、新しいシグナルでイベントリスナーを再登録できるようになります。
次の処理は アンカー接続の再確立 です。for プロパティとアンカー要素が存在する場合、意図的に anchor を null にリセットしてから handleForChange を実行します。この強制的なリセットにより、handleForChange 内の処理が確実に実行され、新しいシグナルでイベントリスナーが再登録されます。
設計判断
この修正は wa-tooltip コンポーネントで既に採用されているパターンに従っています。
PR内のコメントで言及されているように、Tooltipコンポーネントでも同様のDOM移動問題が存在し、同じアプローチで解決されていました。Popoverの実装もTooltipと類似の構造を持つため、実証済みの解決策を適用することで、一貫性のある動作とメンテナンス性の向上を実現しています。
anchor の明示的なリセットは、単に handleForChange を呼び出すだけでは不十分なケースに対応するための措置です。既存のアンカー参照が残っている場合、handleForChange 内の差分検出が変更なしと判断してイベントリスナーの再登録をスキップする可能性があります。null への代入により、確実に「新規接続」として処理されます。
本修正は、Web Componentsのライフサイクルに沿った最小限の変更で、既存の動作に影響を与えることなくDOM移動のシナリオをサポートしています。