Fix memory leaks from Tippy.js instances and unremoved event listeners

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 13:54:38 +08:00
parent 9acd722929
commit 881dccc1ab
5 changed files with 48 additions and 21 deletions

View File

@@ -59,7 +59,7 @@
* with links to account management, my account, and logout. * with links to account management, my account, and logout.
*/ */
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, onBeforeUnmount, ref } from "vue";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import i18next from "@/i18n/i18n"; import i18next from "@/i18n/i18n";
import { useRouter, useRoute } from "vue-router"; import { useRouter, useRoute } from "vue-router";
@@ -106,12 +106,14 @@ const onBtnMyAccountClick = async () => {
await router.push("/my-account"); await router.push("/my-account");
}; };
/** Registers a click listener to close the menu when clicking outside. */ /**
const clickOtherPlacesThenCloseMenu = () => { * Closes the menu when clicking outside. Stored as a named
* function so it can be removed in onBeforeUnmount.
* @param {MouseEvent} event - The click event.
*/
const handleDocumentClick = (event) => {
const acctMgmtButton = document.getElementById("acct_mgmt_button"); const acctMgmtButton = document.getElementById("acct_mgmt_button");
const acctMgmtMenu = document.getElementById("account_menu"); const acctMgmtMenu = document.getElementById("account_menu");
document.addEventListener("click", (event) => {
if ( if (
acctMgmtMenu && acctMgmtMenu &&
acctMgmtButton && acctMgmtButton &&
@@ -120,7 +122,11 @@ const clickOtherPlacesThenCloseMenu = () => {
) { ) {
acctMgmtStore.closeAcctMenu(); acctMgmtStore.closeAcctMenu();
} }
}); };
/** Registers a click listener to close the menu when clicking outside. */
const clickOtherPlacesThenCloseMenu = () => {
document.addEventListener("click", handleDocumentClick);
}; };
/** Navigates to the Account Admin page. */ /** Navigates to the Account Admin page. */
@@ -161,6 +167,10 @@ onMounted(async () => {
await getIsAdminValue(); await getIsAdminValue();
clickOtherPlacesThenCloseMenu(); clickOtherPlacesThenCloseMenu();
}); });
onBeforeUnmount(() => {
document.removeEventListener("click", handleDocumentClick);
});
</script> </script>
<style> <style>

View File

@@ -306,6 +306,7 @@ export default function cytoscapeMap(
// creat tippy.js // creat tippy.js
let tip; let tip;
cy.on("mouseover", "node", function (event) { cy.on("mouseover", "node", function (event) {
tip?.destroy();
const node = event.target; const node = event.target;
let ref = node.popperRef(); let ref = node.popperRef();
let dummyDomEle = document.createElement("div"); let dummyDomEle = document.createElement("div");
@@ -319,7 +320,8 @@ export default function cytoscapeMap(
if (node.data("label").length > 10) tip.show(); if (node.data("label").length > 10) tip.show();
}); });
cy.on("mouseout", "node", function (event) { cy.on("mouseout", "node", function (event) {
tip?.hide(); tip?.destroy();
tip = null;
}); });
// here we remember and recall positions // here we remember and recall positions

View File

@@ -96,6 +96,7 @@ export default function cytoscapeMapTrace(nodes, edges, graphId) {
// creat tippy.js // creat tippy.js
let tip; let tip;
cy.on("mouseover", "node", function (event) { cy.on("mouseover", "node", function (event) {
tip?.destroy();
const node = event.target; const node = event.target;
let ref = node.popperRef(); let ref = node.popperRef();
let dummyDomEle = document.createElement("div"); let dummyDomEle = document.createElement("div");
@@ -109,6 +110,7 @@ export default function cytoscapeMapTrace(nodes, edges, graphId) {
tip.show(); tip.show();
}); });
cy.on("mouseout", "node", function (event) { cy.on("mouseout", "node", function (event) {
tip.hide(); tip?.destroy();
tip = null;
}); });
} }

View File

@@ -75,6 +75,8 @@ const {
conformanceFileName, conformanceFileName,
} = storeToRefs(conformanceStore); } = storeToRefs(conformanceStore);
let loadingTimerId = null;
// Created logic // Created logic
(async () => { (async () => {
isLoading.value = true; isLoading.value = true;
@@ -109,7 +111,7 @@ const {
} catch (error) { } catch (error) {
console.error("Failed to initialize conformance:", error); console.error("Failed to initialize conformance:", error);
} finally { } finally {
setTimeout(() => (isLoading.value = false), 500); loadingTimerId = setTimeout(() => (isLoading.value = false), 500);
} }
})(); })();
@@ -124,6 +126,7 @@ onMounted(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearTimeout(loadingTimerId);
conformanceLogId.value = null; conformanceLogId.value = null;
conformanceFilterId.value = null; conformanceFilterId.value = null;
conformanceLogCreateCheckId.value = null; conformanceLogCreateCheckId.value = null;

View File

@@ -465,7 +465,7 @@
* and file operations (rename, delete). * and file operations (rename, delete).
*/ */
import { ref, computed, watch, onMounted } from "vue"; import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { useMapCompareStore } from "@/stores/mapCompareStore"; import { useMapCompareStore } from "@/stores/mapCompareStore";
@@ -917,19 +917,29 @@ function getGridSortData(event) {
} }
} }
/**
* Clears the active file selection when clicking outside a list item.
* @param {MouseEvent} e - The click event.
*/
const handleWindowClick = (e) => {
const clickedLi = e.target.closest("li");
if (!clickedLi || !clickedLi.id.startsWith("li")) isActive.value = null;
};
// Mounted // Mounted
onMounted(() => { onMounted(() => {
isLoading.value = true; isLoading.value = true;
store.fetchAllFiles(); store.fetchAllFiles();
window.addEventListener("click", (e) => { window.addEventListener("click", handleWindowClick);
const clickedLi = e.target.closest("li");
if (!clickedLi || !clickedLi.id.startsWith("li")) isActive.value = null;
});
// Add the .scrollbar class to the DataTable tbody // Add the .scrollbar class to the DataTable tbody
const tbodyElement = document.querySelector(".p-datatable-tbody"); const tbodyElement = document.querySelector(".p-datatable-tbody");
tbodyElement?.classList.add("scrollbar"); tbodyElement?.classList.add("scrollbar");
isLoading.value = false; isLoading.value = false;
}); });
onBeforeUnmount(() => {
window.removeEventListener("click", handleWindowClick);
});
</script> </script>
<style scoped> <style scoped>
@reference "../../assets/tailwind.css"; @reference "../../assets/tailwind.css";