Skip to content

feat(map): adopt cooperative gestures to stop trapping page scroll#266

Merged
markdumay merged 1 commit into
mainfrom
feat/cooperative-gestures
Jul 13, 2026
Merged

feat(map): adopt cooperative gestures to stop trapping page scroll#266
markdumay merged 1 commit into
mainfrom
feat/cooperative-gestures

Conversation

@markdumay

Copy link
Copy Markdown
Contributor

Problem

Leaflet binds the wheel to zoom and a single finger to pan. Scrolling the page with the
pointer over a map therefore gets swallowed and the page sticks — the classic embedded-map scroll
trap. It reproduces today on
gethinode.com/docs/components/map: a plain wheel over
the map takes the tiles from zoom 13 to 12 instead of scrolling the page.

This is the same trap gethinode/mod-mermaid#338 removed from inline diagrams in v4.10.0. That PR
described its goal as following "the cooperative-gesture pattern used by embedded maps" — so this
restores the pattern to the map it was named after.

Change

The map now claims only a gesture that is unambiguously meant for it:

Gesture Before After
Wheel over map zooms map (page sticks) page scrolls, hint appears
Ctrl/ + wheel page zooms map zooms
Mouse drag pans map pans map (unchanged)
One finger pans map (page sticks) page scrolls, hint appears
Two fingers pans / pinch-zooms map
+/, keyboard zooms unchanged

A gesture the map ignores briefly shows an overlay — "Use ⌘ + scroll to zoom the map" — so an
ignored gesture does not read as a broken map.

How it works

Every listener runs in the capture phase. Leaflet binds its own handlers deeper in the tree, so
stopping the event here (wheel without a modifier) or withdrawing the handler here (dragging
during a one-finger touch) settles the gesture before Leaflet ever sees it. With the modifier held
the event is simply let through, so Leaflet applies its own tuned zoom rather than a reimplementation
of it.

Notable details

  • The hint stays translatable. The shortcode renders it into data-leaflet-hint-*; only the
    modifier is resolved in the browser, since it depends on the visitor's platform ( vs Ctrl).
    Adds i18n/ for the eight languages Hinode ships, and the i18n mount, which the module lacked.
  • The stylesheet slot. dist/leaflet.scss is Leaflet's vendored CSS, regenerated by
    postinstall, and it occupied the module's single assets/scss/leaflet.scss slot — so the overlay
    styles had nowhere to live. It is remounted as leaflet-vendor.scss, and a hand-written
    assets/scss/leaflet.scss imports it and adds the overlay. The vendored file stays untouched.
  • The overlay class keeps the leaflet- prefix that Hinode's PurgeCSS safelist (/^leaflet-/)
    already covers, and is pointer-events: none + aria-hidden — it is advisory and must never
    swallow the gesture it is explaining.
  • No new shortcode argument: cooperative behaviour is always on. An opt-out would just be a way to
    reintroduce the bug.

Verification

Driven in a real browser against the block reference, with the module replaced locally into a
Hinode host:

Check Result
Plain wheel over map tiles stay at z13, hint shown ✅
Ctrl + wheel tiles go to z12
{modifier} placeholder resolved to on macOS ✅
Overlay styles compiled position: absolute, scrim, pointer-events: none
Vendored Leaflet CSS still applied container styled, 28 tiles loaded ✅

pnpm test passes.

Review notes

The seven non-English translations are mine and have not been reviewed by a native speaker
worth a look, particularly zh-hans/zh-hant. The hint timing (2s) and the dark scrim are easy to
adjust.

🤖 Generated with Claude Code

Leaflet binds the wheel to zoom and a single finger to pan, so scrolling the page with the
pointer over a map got swallowed and the page stuck — the scroll trap that mod-mermaid removed
from inline diagrams in v4.10.0. The map now claims only a gesture that is unambiguously meant
for it:

- Wheel scrolls the page; Ctrl/Cmd + wheel zooms the map.
- Mouse drag still pans, unchanged.
- One finger scrolls the page; two fingers pan and pinch-zoom.

A gesture the map ignores briefly shows an overlay explaining how to address the map instead, so
it does not read as broken. The listeners run in the capture phase, which settles the gesture
before Leaflet's own handlers further down the tree ever see it.

The hint text is rendered by the shortcode, so it stays translatable; only the modifier is
resolved in the browser, because it depends on the visitor's platform. Adds i18n for the eight
languages Hinode ships, and mounts i18n, which the module did not do before.

Leaflet's vendored stylesheet is remounted as leaflet-vendor.scss so that assets/scss/leaflet.scss
— the module's single stylesheet slot — can import it and add the overlay styles, leaving the
file that postinstall regenerates untouched. The overlay class keeps the leaflet- prefix that
Hinode's PurgeCSS safelist already covers.

Verified in a browser on the block reference: a plain wheel leaves the tiles at zoom 13 and shows
the hint, Ctrl + wheel takes them to 12, and both the overlay styles and the vendored Leaflet CSS
compile.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@markdumay markdumay merged commit 46805d9 into main Jul 13, 2026
8 checks passed
@markdumay markdumay deleted the feat/cooperative-gestures branch July 13, 2026 13:24
@markdumay

Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version 3.1.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant