aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Utility/html.ts
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-03-01 14:20:08 -0800
committerFuwn <[email protected]>2026-03-01 15:24:03 -0800
commit3b10a1f47fd5838fe3b94c19673a52610b88cf1e (patch)
treed468a1fc12290e38686b255194ff6596b58cbf01 /src/lib/Utility/html.ts
parentperf(match): fast-path exact normalised title matches (diff)
downloaddue.moe-3b10a1f47fd5838fe3b94c19673a52610b88cf1e.tar.xz
due.moe-3b10a1f47fd5838fe3b94c19673a52610b88cf1e.zip
perf: optimise list hot paths and shared timers
Diffstat (limited to 'src/lib/Utility/html.ts')
-rw-r--r--src/lib/Utility/html.ts109
1 files changed, 79 insertions, 30 deletions
diff --git a/src/lib/Utility/html.ts b/src/lib/Utility/html.ts
index 8d60d3b6..ccdfae8d 100644
--- a/src/lib/Utility/html.ts
+++ b/src/lib/Utility/html.ts
@@ -4,39 +4,88 @@ import { get } from 'svelte/store';
export const nbsp = (str: string) => str.replace(/ /g, '&nbsp;');
export const createHeightObserver = (details = true) => {
- document.querySelectorAll('.list').forEach((element) => {
- if (
- !(
- element as unknown as {
- dataset: { observed: string };
- }
- ).dataset.observed
- ) {
- new ResizeObserver((entries) => {
- entries.forEach((entry) => {
- const element = entry.target as HTMLElement;
-
- if (get(settings).displayLimitListHeight) {
- element.style.height = 'auto';
-
- const elementBound = element.getBoundingClientRect();
- const height = window.innerHeight - elementBound.top - 2.5 * 16;
-
- if (elementBound.height > height) element.style.height = `${height}px`;
- }
- });
- }).observe(element);
+ const observedElements = new Set<HTMLElement>();
+ const resizeObservers = new Map<HTMLElement, ResizeObserver>();
+ const detailObservers = new Map<HTMLElement, MutationObserver>();
+
+ const applyHeightLimit = (target: HTMLElement) => {
+ if (!get(settings).displayLimitListHeight) {
+ target.style.height = 'auto';
+
+ return;
+ }
+
+ target.style.height = 'auto';
+
+ const elementBound = target.getBoundingClientRect();
+ const height = window.innerHeight - elementBound.top - 2.5 * 16;
- if (details)
- new MutationObserver((mutations) => {
- mutations.forEach((mutation) => {
- const element = mutation.target as HTMLDetailsElement;
+ if (elementBound.height > height) target.style.height = `${height}px`;
+ };
- if (element.tagName === 'DETAILS' && !element.open) element.style.height = 'auto';
- });
- }).observe(element, { attributes: true });
+ const observeElement = (element: HTMLElement) => {
+ if (element.dataset.observed) return;
+
+ const resizeObserver = new ResizeObserver((entries) => {
+ entries.forEach((entry) => {
+ const target = entry.target as HTMLElement;
+
+ applyHeightLimit(target);
+ });
+ });
+
+ resizeObserver.observe(element);
+ resizeObservers.set(element, resizeObserver);
+
+ if (details) {
+ const detailsObserver = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ const target = mutation.target as HTMLDetailsElement;
+
+ if (target.tagName === 'DETAILS' && !target.open) target.style.height = 'auto';
+ });
+ });
- element.setAttribute('data-observed', 'true');
+ detailsObserver.observe(element, { attributes: true });
+ detailObservers.set(element, detailsObserver);
}
+
+ element.dataset.observed = 'true';
+
+ observedElements.add(element);
+ applyHeightLimit(element);
+ };
+
+ document.querySelectorAll<HTMLElement>('.list').forEach(observeElement);
+
+ const mutationObserver = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ mutation.addedNodes.forEach((node) => {
+ if (!(node instanceof HTMLElement)) return;
+
+ if (node.matches('.list')) observeElement(node);
+
+ node.querySelectorAll<HTMLElement>('.list').forEach(observeElement);
+ });
+ });
});
+
+ mutationObserver.observe(document.body, { childList: true, subtree: true });
+
+ const unsubscribeSettings = settings.subscribe(() => {
+ observedElements.forEach((element) => applyHeightLimit(element));
+ });
+
+ return () => {
+ unsubscribeSettings();
+ mutationObserver.disconnect();
+ resizeObservers.forEach((observer) => observer.disconnect());
+ detailObservers.forEach((observer) => observer.disconnect());
+
+ observedElements.forEach((element) => {
+ element.style.height = 'auto';
+
+ delete element.dataset.observed;
+ });
+ };
};