You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Security
maplibre-gl is now required at ^6.9.0, which closes CVE-2026-85061 (CVSS 9.6). DOM.sanitize() in every version up to and including 6.4.0 walked the live NamedNodeMap
returned by elem.attributes while removing entries from it, so the indexes shifted and an
adjacent dangerous attribute was skipped. Two consecutive ones let an onload or ontoggle
survive sanitization and run when the attribution control wrote the string into innerHTML.
Attribution text comes from the style document, so any deployment that renders a third-party
or user-supplied style was one map load away from script execution in the viewer's session.
Fixed upstream in 6.4.1.
Added
Overlapping map features now open one popup with a switcher instead of only the topmost.
Clicking where several interactive layers overlap collects every hit into a popupStack
on the map store (SET_POPUP_STACK, SET_POPUP_INDEX) and renders them through a single MapPopupStack, with a ‹ 2 of 5 · Building › header when there is more than one. Entries
are ordered by MapLayerClickPriority, deduplicated by id and capped at 10 per click.
Clusters contribute one entry listing their contents with a zoom-to-expand button. New MapPopupStack message namespace in en, es and fr.
PopupEntry and PopupStack in …/types/map, the descriptor a layer returns from its
click resolver.
An optional header slot on Sensor, Comment and MapFeaturePopoverMenu, so the popup
switcher renders inside the body's own card. The slot also carries the popup's single close
button: a body that receives a header hides its own, so every map popup closes the same way.
A 3D-buildings toggle in the map settings panel. New show3dBuildings on the map store
(default true) with an UPDATE_SHOW_3D_BUILDINGS action, a BuildingVisibility control
under Map Style, and a MapCustomization.buildings3d message in en, es and fr.
Switching it off hides the layer, which also takes it out of hit-testing.
TERRAIN_SPEC from …/map/utils/mapStyleSpec and SUBDIVISION_LINE_OPACITY from …/map/src/MapLayers/src/CountryLayer, so a consumer overriding either can reuse the same values.
writeModelMatrix in @collabdt/core/core/components/viewers/map/utils/modelMatrix builds the
model-to-mercator matrix for a three.js custom layer, replacing the map.transform.getMatrixForModel that maplibre 6 removed. It writes into a caller-owned Matrix4 so a render loop allocates nothing.
Changed
resolveBounds (…/viewers/map/utils/validateBounds) now returns flat bounds [minLng, minLat, maxLng, maxLat] | undefined instead of LngLatBoundsLike, normalising a
nested [[minLng, minLat], [maxLng, maxLat]] input to that shape. Nested bounds and LngLatBounds instances are not assignable to the map's maxBounds prop under maplibre-gl@6. Its fallbackBounds parameter takes the same flat tuple.
SiteContextMenu is renamed SiteMenu, along with its file, its props type and its
i18n namespace. It is anchored by the map popup rather than the cursor, so the
"context menu" name was misleading; its x / y props are gone with the fixed
positioning they drove. See Migration.
MapClickManager.register now takes a PopupResolver that returns PopupEntry[], not a
side-effecting ClickCallback. Clicks collect entries from every layer that was hit rather
than stopping at the highest-priority one, so MapLayerClickPriority orders the stack instead
of deciding which single layer wins. registerLegacy keeps the old callback contract for
handlers that own their own UI, and MapLayerClickPriority.ActiveTool is still exclusive:
a click while a tool is active opens nothing. See Migration.
maplibre 6 is ESM-only and no longer inlines its tile-parsing worker the way 5 did; the
worker now ships as a separate maplibre-gl-worker.mjs chunk that has to be resolvable at
runtime. See Migration.
maplibre-gl moves from ^5.0.0 to ^6.9.0 in peerDependencies. A consumer has to
upgrade in lockstep; the new range cannot be satisfied by any release still carrying the
advisory above.
react-map-gl moves from ^8.0.0 to ^8.1.3 in peerDependencies. maplibre 6 removed
the internal map.transform property — Map now composes a Camera instead of extending it —
and react-map-gl read it on every camera event. Versions below 8.1.3 therefore throw Cannot read properties of undefined (reading 'center') on any pan, zoom or flyTo; 8.1.3
reads the public getters instead.
The country-subdivision border fades out above zoom 9 and stops drawing at 10. The
subdivision fill is unchanged and still hit-tests at every zoom, so camera tracking and hover
keep resolving countrySubdivision with no border on screen.
Map-level pointer listeners use mouseout. In maplibre 6 mouseenter and mouseleave are
layer-scoped events and require a layer id, so they never fired when bound to the map itself.
Fixed
Streets no longer draws buildings twice. Basemaps such as maptiler streets-v2 extrude
the building source-layer themselves, so the basemap's own extrusion is dropped before maptiler-3d-buildings is added and only ours renders.
Terrain is removed while the globe projection is active. The globe has no fog matrix, so
maplibre logged calculateFogMatrix is not supported on globe projection and drew a terrain
pass that produced nothing. Terrain is restored when the map returns to mercator.
MapHoverManager.destroy() removed a mouseenter listener it had never registered, leaving
its mousemove handler bound to the map for the life of the page.
Removed
The global-borders-region layer. Its admin_level 3–4 lines drew a second province border
on top of the subdivision outline; the subdivision layer is the org-coloured, hover-aware one,
and global-borders-country still draws country outlines.
Migration
A caller of resolveBounds that relied on its nested input coming back unchanged now receives
the flattened equivalent, and a fallbackBounds passed as a nested array or a LngLatBounds
must be written flat.
Rename any import of SiteContextMenu to SiteMenu (the file moves from SiteContextMenu.tsx to SiteMenu.tsx, and SiteContextMenuProps becomes SiteMenuProps).
Drop the x / y props. A message catalog that overrides the SiteContextMenu namespace
must rename that key to SiteMenu, or its overrides fall back to the bundled strings.
A custom map layer that called mapClickManager.register must return PopupEntry[]
instead of opening its own <Popup>. Build the descriptor in the layer so render keeps
that layer's state and hooks:
render receives the stack's switcher bar and must place it inside its own card, so
per-card chrome (the sensor ring, a coloured border) wraps the bar too. Sensor, Comment
and MapFeaturePopoverMenu take it as a new optional header prop. id is the dedup key, so a layer drawn as several maplibre layers (fill + outline) must
produce the same id for the same feature. A body that reads live data should render a
component rather than capture values in the closure, which is built once per click.
Registrations that open their own UI and consume the click — an active tool, for example —
move to registerLegacy with no other change.
Upgrade maplibre-gl to ^6.9.0 and regenerate your lockfile in the same commit. Bumping package.json alone leaves the vulnerable version resolved and the advisory open.
Upgrade react-map-gl to ^8.1.3 at the same time. The two are not independent: maplibre 6
with react-map-gl 8.1.1 or earlier crashes on the first camera movement.
Call setWorkerUrl() once, at application startup. maplibre 6 loads tile parsing from a
separate worker chunk, and under a bundler import.meta.url does not resolve into the module
graph, so its own defaultWorkerUrl() returns an empty string and no tiles load. The URL is
bundler-specific, so this package does not guess it. On Next.js, copy both maplibre-gl-worker.mjs and its sibling maplibre-gl-shared.mjs out of maplibre-gl/dist
into public/maplibre/ from a pre-build script, then point at the served path:
The worker imports that sibling on its first line, and Turbopack does not emit it beside a
hashed worker asset, so the new URL('maplibre-gl/dist/maplibre-gl-worker.mjs', import.meta.url) form that works under plain webpack fails there. Vite instead wants import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url'.
maplibre 6 drops its default export. import maplibregl from 'maplibre-gl' becomes import * as maplibregl from 'maplibre-gl', and the same for import type.
map.transform.getMatrixForModel is gone. A custom layer that builds its own model matrix
should call writeModelMatrix(out, lngLat, altitude) instead.
A map-level map.on('mouseleave', fn) or map.on('mouseenter', fn) must become map.on('mouseout', fn) / map.on('mousemove', fn), or pass a layer id to keep the old event.