Add a core-owned, map-anchored popup service driven over the bus - #423
Draft
CarsonDavis wants to merge 6 commits into
Draft
Add a core-owned, map-anchored popup service driven over the bus#423CarsonDavis wants to merge 6 commits into
CarsonDavis wants to merge 6 commits into
Conversation
A plugin opens a card by sending plain data to map:showPopup and learns how it closed from the request's own promise. The card is plain DOM under the app's stylesheet, sanitized with DOMPurify's defaults, tracks its anchor across camera moves on both engines, and is dismissed by Escape, its close button, or a click on empty map; a click on a feature never dismisses it. Only the plugin that opened a card can retract it, and core closes a plugin's card when that plugin is destroyed.
…to feature/298-map-popup-service-v2
…to feature/298-map-popup-service-v2
…to feature/298-map-popup-service-v2
This was referenced Sep 10, 2026
…merge two wiring-failure cases
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A plugin can now ask core for an anchored popup card with a plain JSON request and get the outcome back on the request's own promise.
Closes #298
No plugin in this PR opens a card yet; AOI moves onto it in the next PR. On the demo mission, open one from the console with
window.mmgisAPI.request('map:showPopup', { latlng: { lat: 18, lng: 77 }, title: 'X', html: '<p>hi</p>', primaryAction: { label: 'Go' } }). That is the address-less path, somap:hidePopupfrom a tool's handle answers false for it.Plain-data request, promise answer
Previously an anchored popup meant a plugin handing a live mount function and its own component tree across the core/plugin boundary through
map:addOverlay. The payload is now JSON only, and the promise stays pending until the card closes. It resolvesprimaryorsecondaryfor a button,dismisswhen the user closes it (X, Escape, a click on empty map), orclosedwhen code does (a replacing request,map:hidePopup, teardown). Nothing is broadcast.Core owns the card's DOM
Previously each plugin styled its own popup. The card is now plain DOM under the app's theme tokens, and
htmlruns through DOMPurify defaults plus forbidden<style>andpopover: inlinestyleand tables survive,<script>,onclickandjavascript:are stripped. Links inside open in a new tab.The card tracks its anchor
Previously only
moveendmoved AOI's popup, so it snapped at the end of a drag. It now followsmove,moveend,zoomendand resize, clamps to the map, and flips below the anchor when it would clip the top. It parks off-screen while the anchor is off-map or a zoom runs: Leaflet's animated zoom emits nomove, deck.gl emits every frame and keeps tracking.Who can close a card, and when core does
map:hidePopupreturns false unless the slot holds your own card. Core also closes cards on its own: it hides the card onplugins:destroyedfor that owner, and empties the slot onplugins:allDestroyedand on mission re-init. Both are covered by tests only.Clicks and keys
A click carrying a feature never dismisses the card; a click on empty map does. A card opened from a click does not dismiss itself on that click, because the adapters fan clicks out only to subscribers alive when the click was reported. The card is a
role="dialog": focus moves to it, Escape closes it and returns focus, Tab leaves normally, nothing is trapped.Panels paint above the card
Compact-layout panel regions now get their own stacking level. Previously a card could paint over them.
Decisions to review
map:hidePopupwith no address.target="_blank" rel="noopener", keeping the HTML policy in one place.IMapEngine.addOverlay(). It projects withlatLngToContainerPointand subscribes to the camera events directly, so core now carries two lat/lng-anchored overlay paths with different clipping, stacking and click semantics.zoomendwhile deck.gl tracks every frame; the alternative is Leaflet'szoomanimplus a CSS transform, as Leaflet's own popups do.dismissbefore the new request lands, so a plugin clearing state ondismisswould wipe what its next card just set.