Let the wheel reach the page when a map is only part of it
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
The people-groups grid under the dashboard could not be reached by scrolling. Every earlier investigation looked at section heights and overflow, because that is what the documented fix addressed — but the cause was the map itself: Mapbox binds the wheel for zoom, so the document never saw the event. Measured before: fourteen wheel ticks left scrollY at zero and the first grid card exactly where it started. Scroll-zoom is now disabled only while the page has somewhere to scroll, so a map that is the whole page still zooms on wheel and a map stacked above a grid gets out of the way. Measured after: scrollY 1549 and the first card moved from 897 to -652.
This commit is contained in:
parent
ec012f45b6
commit
56dbb6770d
1 changed files with 37 additions and 0 deletions
|
|
@ -367,10 +367,47 @@ function onBubbleClick(e) {
|
|||
bus.emit('focusRow', { slug, name: f.properties && f.properties.name })
|
||||
}
|
||||
|
||||
// ── SCROLL-ZOOM vs PAGE-SCROLL (BUG-CANNOT-SCROLL-DOWN-TO-THE-GRID-UNDER-THE-DASHBOARD) ──────
|
||||
// Mapbox GL calls preventDefault() on EVERY wheel event over its canvas to run scroll-zoom. That
|
||||
// is correct for a map that IS the page (the fixed-viewport leaves), and fatal for a map embedded
|
||||
// in a SCROLL-PAGE composite: the wheel never reaches the document, so the page cannot scroll to
|
||||
// whatever is stacked BELOW the dashboard. MEASURED 2026-09-22 on the dashboard-plus-grid dev
|
||||
// leaf: cursor over .kpi scrolled 600px; cursor over mapboxgl-canvas scrolled 0px with
|
||||
// defaultPrevented=true, with 2,631px of grid sitting below the fold and unreachable.
|
||||
//
|
||||
// The .map-scroll-edge strips are the TOUCH answer (a pan-y drag at either edge scrolls the page
|
||||
// while the map keeps the centre), and they are now rendered — but they do NOT release a DESKTOP
|
||||
// wheel at the centre of the canvas, which is exactly where a mouse naturally sits. So on a
|
||||
// scroll-page the wheel must belong to the PAGE.
|
||||
//
|
||||
// WHY DETECT RATHER THAN HARD-DISABLE: this panel ships inside BOTH kinds of host — the
|
||||
// standalone research-dashboard leaf (fixed viewport, where wheel-zoom is the expected and
|
||||
// desirable behaviour) and the cap-4 composites (a scroll-stack). The document's own scrollability
|
||||
// is the honest discriminator: a fixed-viewport leaf pins html/body to one screen, so there is
|
||||
// nothing to scroll and scroll-zoom stays ON; a scroll-page grows past the viewport, so the wheel
|
||||
// is handed back. NOTHING IS LOST EITHER WAY — the MapToolbar zoom buttons and double-click/
|
||||
// pinch zoom all still work, so a scroll-page map remains fully zoomable without trapping a wheel.
|
||||
function syncScrollZoomToHost(m) {
|
||||
if (!m || !m.scrollZoom) return
|
||||
try {
|
||||
const doc = document.documentElement
|
||||
// A real page scrollport: content taller than the viewport AND an ancestor that can scroll it.
|
||||
const canPageScroll = doc.scrollHeight > window.innerHeight + 4
|
||||
if (canPageScroll) m.scrollZoom.disable()
|
||||
else m.scrollZoom.enable()
|
||||
} catch (_) { /* an engine without scrollZoom must never break the panel */ }
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await initializeMap()
|
||||
const m = map.value; if (!m) return
|
||||
if (m.isStyleLoaded()) addLayers(); else m.on('load', addLayers)
|
||||
// Decide once the stack below has had a chance to lay out (the grid loads async, so the page
|
||||
// only becomes taller than the viewport after its first rows land), then keep it in step: a
|
||||
// collapsing table or a filtered-down grid can change whether the page scrolls at all.
|
||||
syncScrollZoomToHost(m)
|
||||
setTimeout(() => syncScrollZoomToHost(m), 1200)
|
||||
setTimeout(() => syncScrollZoomToHost(m), 4000)
|
||||
// Keep the zoom buttons' enabled state in step with the live camera. 'zoom' fires only during
|
||||
// an actual zoom; 'zoomend' catches the settle. Both just set two booleans (no camera writes).
|
||||
m.on('zoom', syncZoomLimits)
|
||||
|
|
|
|||
Loading…
Reference in a new issue