fix(editor): memory leak caused by missing unsubscription from autoUpdate (#13205)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved resource cleanup for floating UI elements and popups to
prevent potential memory leaks and ensure proper disposal when
components are removed or updated.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Cats Juice
2025-07-15 10:27:48 +08:00
committed by GitHub
parent 7cff8091e4
commit 36bd8f645a
4 changed files with 17 additions and 3 deletions
@@ -85,6 +85,8 @@ export class MenuSubMenu extends MenuFocusable {
.catch(err => console.error(err)); .catch(err => console.error(err));
}); });
this.menu.openSubMenu(menu); this.menu.openSubMenu(menu);
// in case that the menu is not closed, but the component is removed,
this.disposables.add(unsub);
} }
protected override render(): unknown { protected override render(): unknown {
@@ -116,6 +116,7 @@ export class EdgelessTemplateButton extends EdgelessToolbarToolMixin(
`; `;
private _cleanup: (() => void) | null = null; private _cleanup: (() => void) | null = null;
private _autoUpdateCleanup: (() => void) | null = null;
private _prevTool: ToolOptionWithType | null = null; private _prevTool: ToolOptionWithType | null = null;
@@ -128,6 +129,11 @@ export class EdgelessTemplateButton extends EdgelessToolbarToolMixin(
return [TemplateCard1[theme], TemplateCard2[theme], TemplateCard3[theme]]; return [TemplateCard1[theme], TemplateCard2[theme], TemplateCard3[theme]];
} }
override connectedCallback() {
super.connectedCallback();
this.disposables.add(() => this._autoUpdateCleanup?.());
}
private _closePanel() { private _closePanel() {
if (this._openedPanel) { if (this._openedPanel) {
this._openedPanel.remove(); this._openedPanel.remove();
@@ -175,8 +181,8 @@ export class EdgelessTemplateButton extends EdgelessToolbarToolMixin(
requestAnimationFrame(() => { requestAnimationFrame(() => {
const arrowEl = panel.renderRoot.querySelector('.arrow') as HTMLElement; const arrowEl = panel.renderRoot.querySelector('.arrow') as HTMLElement;
this._autoUpdateCleanup?.();
autoUpdate(this, panel, () => { this._autoUpdateCleanup = autoUpdate(this, panel, () => {
computePosition(this, panel, { computePosition(this, panel, {
placement: 'top', placement: 'top',
middleware: [offset(20), arrow({ element: arrowEl }), shift()], middleware: [offset(20), arrow({ element: arrowEl }), shift()],
@@ -160,6 +160,7 @@ export class ChatCopyMore extends WithDisposable(LitElement) {
mainAxis: 0, mainAxis: 0,
crossAxis: -100, crossAxis: -100,
}); });
this.disposables.add(() => this._morePopper?.dispose());
} }
} }
} }
@@ -54,6 +54,8 @@ export class EdgelessCopilotWidget extends WidgetComponent<RootBlockModel> {
private _selectionModelRect!: DOMRect; private _selectionModelRect!: DOMRect;
private _autoUpdateCleanup: (() => void) | null = null;
groups: AIItemGroupConfig[] = []; groups: AIItemGroupConfig[] = [];
get gfx() { get gfx() {
@@ -145,7 +147,8 @@ export class EdgelessCopilotWidget extends WidgetComponent<RootBlockModel> {
const originMaxHeight = window.getComputedStyle(panel).maxHeight; const originMaxHeight = window.getComputedStyle(panel).maxHeight;
autoUpdate(referenceElement, panel, () => { this._autoUpdateCleanup?.();
this._autoUpdateCleanup = autoUpdate(referenceElement, panel, () => {
computePosition(referenceElement, panel, { computePosition(referenceElement, panel, {
placement: 'bottom-start', placement: 'bottom-start',
middleware: [ middleware: [
@@ -267,6 +270,8 @@ export class EdgelessCopilotWidget extends WidgetComponent<RootBlockModel> {
this._copilotPanel = null; this._copilotPanel = null;
}) })
); );
this._disposables.add(() => this._autoUpdateCleanup?.());
} }
determineInsertionBounds(width = 800, height = 95) { determineInsertionBounds(width = 800, height = 95) {