From e273d451cc2de8102897318ebcf83e3297b0d07f Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Fri, 2 Oct 2026 09:21:04 +0000 Subject: [PATCH] Phone legend: shrinking the detail sheet past its content no longer scrolls the page Reported from a phone (Home and Pray, the people-group detail sheet): dragging the handle down worked until the sheet became shorter than its content; from then on the page scrolled while the finger kept dragging. - The sheet and the detail panel's scrollers (.people-group-detail, .detail-content) contain their overscroll, so a list that reaches its end can never chain a scroll to the page. - The drag handlers on the sheet and its grab lip listen in the CAPTURE phase, so every touch reaches the drag first and no child's handler can stop one from being claimed. --- library/components/PeopleGroupDetail.vue | 7 +++++-- library/components/legends/LegendMobile.vue | 23 ++++++++++++--------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/library/components/PeopleGroupDetail.vue b/library/components/PeopleGroupDetail.vue index 2279e605a..c16d26b0a 100644 --- a/library/components/PeopleGroupDetail.vue +++ b/library/components/PeopleGroupDetail.vue @@ -119,7 +119,7 @@ import { getApiBaseUrl } from '../utils/apiBaseUrl.js'; const { t, locale } = useI18n(); useShadowStyles(` -.people-group-detail{display:flex;flex-direction:column;height:100%;overflow-y:auto;} +.people-group-detail{display:flex;flex-direction:column;height:100%;overflow-y:auto;overscroll-behavior:contain;} .people-group-detail.dark-mode{background:#1a1a2e;color:#eee;} .detail-header{display:flex;justify-content:space-between;align-items:flex-start;padding:16px;border-bottom:1px solid #e0e0e0;background:white;position:sticky;top:0;z-index:10;} .dark-mode .detail-header{background:#16213e;border-bottom-color:#2d2d4e;} @@ -133,7 +133,7 @@ useShadowStyles(` .dark-mode .close-button{color:#aaa;} .close-button:hover{color:#333;} .dark-mode .close-button:hover{color:#fff;} -.detail-content{padding:6px 12px 12px;flex:1;overflow-y:auto;} +.detail-content{padding:6px 12px 12px;flex:1;overflow-y:auto;overscroll-behavior:contain;} .dark-mode .detail-content{background:#1a1a2e;} .quick-stats{display:flex;flex-direction:column;gap:4px;margin-bottom:8px;} .stat-item{display:flex;justify-content:space-between;align-items:baseline;font-size:13px;} @@ -467,6 +467,8 @@ function handleImageError(event) { flex-direction: column; height: 100%; overflow-y: auto; + /* a scroll that reaches this panel's end never chains to the page (the mobile sheet's detail view) */ + overscroll-behavior: contain; } /* Header Section */ @@ -540,6 +542,7 @@ function handleImageError(event) { padding: 6px 12px 12px; flex: 1; overflow-y: auto; + overscroll-behavior: contain; } /* Quick Stats */ diff --git a/library/components/legends/LegendMobile.vue b/library/components/legends/LegendMobile.vue index 71ae01970..25da0b148 100644 --- a/library/components/legends/LegendMobile.vue +++ b/library/components/legends/LegendMobile.vue @@ -118,7 +118,7 @@ useShadowStyles(` max-height reads --legend-sheet-top-reserve, set on this element from the TOP_RESERVE_PX constant in the script — the SAME number the touch-drag clamp uses, so CSS and JS can never disagree about how tall the sheet may get. */ -.legend-mobile-sheet{position:absolute;bottom:0;inset-inline-start:0;inset-inline-end:0;z-index:1000;background:white;border-top-left-radius:var(--map-radius,16px);border-top-right-radius:var(--map-radius,16px);box-shadow:0 -4px 20px rgba(0,0,0,0.2);transition:height 0.3s ease-out;display:flex;flex-direction:column;overflow:hidden;max-height:calc(100% - var(--legend-sheet-top-reserve,80px));} +.legend-mobile-sheet{overscroll-behavior:contain;position:absolute;bottom:0;inset-inline-start:0;inset-inline-end:0;z-index:1000;background:white;border-top-left-radius:var(--map-radius,16px);border-top-right-radius:var(--map-radius,16px);box-shadow:0 -4px 20px rgba(0,0,0,0.2);transition:height 0.3s ease-out;display:flex;flex-direction:column;overflow:hidden;max-height:calc(100% - var(--legend-sheet-top-reserve,80px));} /* While a finger is resizing the sheet the height must be applied INSTANTLY — with the 0.3s transition left on, the measured height lagged the finger by 14-30px (mobile-sheet-probe baseline 2026-09-16) and the release logic read a @@ -984,12 +984,15 @@ onBeforeUnmount(() => { if (_creditTimer) { clearTimeout(_creditTimer); _creditT v-if="legendState !== 'collapsed'" class="sheet-grab-lip" aria-hidden="true" - @touchstart="onSheetTouchStart" - @touchmove="onSheetTouchMove" - @touchend="onSheetTouchEnd" - @touchcancel="onSheetTouchEnd" + @touchstart.capture="onSheetTouchStart" + @touchmove.capture="onSheetTouchMove" + @touchend.capture="onSheetTouchEnd" + @touchcancel.capture="onSheetTouchEnd" > -
{ if (_creditTimer) { clearTimeout(_creditTimer); _creditT class="legend-mobile-sheet" :class="[legendState, { 'sheet-dark': isDark, dragging: isDragging, fit: fitOpen }]" :style="{ height: fitOpen ? 'auto' : legendHeight, '--legend-sheet-top-reserve': TOP_RESERVE_PX + 'px' }" - @touchstart="onSheetTouchStart" - @touchmove="onSheetTouchMove" - @touchend="onSheetTouchEnd" - @touchcancel="onSheetTouchEnd" + @touchstart.capture="onSheetTouchStart" + @touchmove.capture="onSheetTouchMove" + @touchend.capture="onSheetTouchEnd" + @touchcancel.capture="onSheetTouchEnd" >