diff --git a/contao/templates/frontend/event_filter.html.twig b/contao/templates/frontend/event_filter.html.twig index 0d20126..894a266 100644 --- a/contao/templates/frontend/event_filter.html.twig +++ b/contao/templates/frontend/event_filter.html.twig @@ -81,6 +81,11 @@ #eventfilters .eventfilter-reset[hidden] { display: none; } + + #eventfilters .ss-content, + #eventfilters .ss-list { + overscroll-behavior: contain; + } @@ -332,5 +337,74 @@ applyFilter({ type: 'all', value: 'all' }); }); + const getDropdownFromTarget = (target) => { + if (!(target instanceof Element)) { + return null; + } + + return target.closest('#eventfilters .ss-content, #eventfilters .ss-list'); + }; + + const shouldBlockBoundaryScroll = (dropdown, delta) => { + if (!dropdown || dropdown.scrollHeight <= dropdown.clientHeight) { + return false; + } + + const top = dropdown.scrollTop; + const height = dropdown.clientHeight; + const scrollHeight = dropdown.scrollHeight; + + const atTop = top <= 0; + const atBottom = top + height >= scrollHeight - 1; + + return (atTop && delta < 0) || (atBottom && delta > 0); + }; + + let lastTouchY = null; + + document.addEventListener('wheel', (event) => { + const dropdown = getDropdownFromTarget(event.target); + + if (shouldBlockBoundaryScroll(dropdown, event.deltaY)) { + event.preventDefault(); + } + }, { passive: false, capture: true }); + + document.addEventListener('touchstart', (event) => { + const dropdown = getDropdownFromTarget(event.target); + + if (!dropdown || event.touches.length === 0) { + lastTouchY = null; + return; + } + + lastTouchY = event.touches[0].clientY; + }, { passive: true, capture: true }); + + document.addEventListener('touchmove', (event) => { + const dropdown = getDropdownFromTarget(event.target); + + if (!dropdown || event.touches.length === 0 || lastTouchY === null) { + return; + } + + const currentTouchY = event.touches[0].clientY; + const delta = lastTouchY - currentTouchY; + + if (shouldBlockBoundaryScroll(dropdown, delta)) { + event.preventDefault(); + } + + lastTouchY = currentTouchY; + }, { passive: false, capture: true }); + + document.addEventListener('touchend', () => { + lastTouchY = null; + }, { passive: true, capture: true }); + + document.addEventListener('touchcancel', () => { + lastTouchY = null; + }, { passive: true, capture: true }); + applyFilter(currentFilter);