From: PhiTux Date: Fri, 29 Aug 2025 17:08:40 +0000 (+0200) Subject: changed buggy boostrap scrollspy to custom scrollspy X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=82e414529524ac0bca0675cf7edc0d1c9f2dcca0;p=DailyTxT.git changed buggy boostrap scrollspy to custom scrollspy --- diff --git a/frontend/src/routes/(authed)/+layout.svelte b/frontend/src/routes/(authed)/+layout.svelte index 56bcea7..47c746d 100644 --- a/frontend/src/routes/(authed)/+layout.svelte +++ b/frontend/src/routes/(authed)/+layout.svelte @@ -84,7 +84,66 @@ } let settingsModal; - let scrollSpy; + + // Custom ScrollSpy state (manual implementation to avoid flicker) + let activeSettingsSection = $state('appearance'); + let settingsSections = []; + let removeScrollListener = null; + let lastComputedSection = 'appearance'; + + function computeActiveSection(container) { + if (!container || settingsSections.length === 0) return; + // Activation line: a bit below the top to give stability + const activationY = container.scrollTop + container.clientHeight * 0.18; // 18% viewport height + let current = settingsSections[0].id; + for (let i = 0; i < settingsSections.length; i++) { + const sec = settingsSections[i]; + if (sec.offsetTop <= activationY) { + current = sec.id; + } else { + break; + } + } + // Hysteresis: only update if different for stability + if (current !== lastComputedSection) { + lastComputedSection = current; + activeSettingsSection = current; + } + } + + function initSettingsScrollSpy() { + const container = document.getElementById('settings-content'); + if (!container) return; + settingsSections = Array.from(container.querySelectorAll(':scope > div[id]')); + if (settingsSections.length === 0) return; + // Initial compute + computeActiveSection(container); + let raf = 0; + const onScroll = () => { + if (raf) cancelAnimationFrame(raf); + raf = requestAnimationFrame(() => computeActiveSection(container)); + }; + container.addEventListener('scroll', onScroll, { passive: true }); + removeScrollListener = () => { + container.removeEventListener('scroll', onScroll); + if (raf) cancelAnimationFrame(raf); + }; + // Recompute on resize for robustness + const onResize = () => computeActiveSection(container); + window.addEventListener('resize', onResize); + const originalRemove = removeScrollListener; + removeScrollListener = () => { + originalRemove && originalRemove(); + window.removeEventListener('resize', onResize); + }; + } + + function destroySettingsScrollSpy() { + removeScrollListener && removeScrollListener(); + removeScrollListener = null; + settingsSections = []; + } + function openSettingsModal() { $tempSettings = JSON.parse(JSON.stringify($settings)); aLookBackYears = $settings.aLookBackYears.toString(); @@ -92,29 +151,26 @@ settingsModal = new bootstrap.Modal(document.getElementById('settingsModal')); settingsModal.show(); - // initialize ScrollSpy - document.getElementById('settingsModal').addEventListener('shown.bs.modal', function onShown() { - // Remove the event listener to prevent multiple executions - document.getElementById('settingsModal').removeEventListener('shown.bs.modal', onShown); - + // Initialize custom ScrollSpy + const modalEl = document.getElementById('settingsModal'); + const onShown = () => { + modalEl.removeEventListener('shown.bs.modal', onShown); const height = document.getElementById('modal-body').clientHeight; - document.getElementById('settings-content').style.height = 'calc(' + height + 'px - 2rem)'; - document.getElementById('settings-nav').style.height = 'calc(' + height + 'px - 2rem)'; - document.getElementById('settings-content').style.overflowY = 'auto'; - - // Wait a little longer for all transitions to complete - setTimeout(() => { - // Apply ScrollSpy to the actual scroll area - const settingsContent = document.getElementById('settings-content'); - console.log(settingsContent); - if (scrollSpy) { - scrollSpy.dispose(); // Remove old ScrollSpy - } - scrollSpy = new bootstrap.ScrollSpy(settingsContent, { - target: '#settings-nav' - }); - console.log(scrollSpy); - }, 400); + const contentEl = document.getElementById('settings-content'); + const navEl = document.getElementById('settings-nav'); + if (contentEl && navEl) { + contentEl.style.height = 'calc(' + height + 'px - 2rem)'; + navEl.style.height = 'calc(' + height + 'px - 2rem)'; + contentEl.style.overflowY = 'auto'; + contentEl.scrollTop = 0; + activeSettingsSection = 'appearance'; + // Short timeout to allow layout calculation before reading offsets + setTimeout(initSettingsScrollSpy, 100); + } + }; + modalEl.addEventListener('shown.bs.modal', onShown); + modalEl.addEventListener('hidden.bs.modal', () => { + destroySettingsScrollSpy(); }); } @@ -814,15 +870,35 @@
@@ -1966,4 +2042,23 @@ .modal-footer { border-top: 1px solid rgba(255, 255, 255, 0.2); } + + /* Custom ScrollSpy styles */ + .custom-scrollspy-nav .nav-link { + border-left: 4px solid transparent; + transition: + background-color 0.18s ease, + color 0.18s ease, + border-color 0.25s ease; + will-change: background-color, color, border-color; + } + .custom-scrollspy-nav .nav-link.active { + background-color: rgba(13, 110, 253, 0.15); + color: #0d6efd; + font-weight: 600; + border-left-color: #0d6efd; + } + .custom-scrollspy-nav .nav-link:not(.active):hover { + background-color: rgba(13, 110, 253, 0.05); + }