Created
August 27, 2025 16:20
-
-
Save vgeorge/a725b7769cc548b6e19871eaa933015d to your computer and use it in GitHub Desktop.
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
| import React, { useEffect } from "react"; | |
| import { ReactPlugin } from "../../core/plugin"; | |
| /** | |
| * Mapbox iframe integration issue | |
| * | |
| * Problem: | |
| * - Mapbox checks `options.container instanceof window.HTMLElement` | |
| * - In plugin iframes, DOM elements come from a different realm (iframe’s global object) | |
| * - So instanceof fails, even if container is a valid <div> | |
| * | |
| * Root cause: | |
| * - Each iframe has its own global constructors (HTMLElement, Element, etc.) | |
| * - instanceof comparisons fail across realms | |
| * | |
| * Solution: | |
| * - Temporarily monkey patch HTMLElement in this frame | |
| * - Set the container’s prototype to match the patched constructor | |
| * - Restore original constructors after map creation | |
| */ | |
| declare global { | |
| interface Window { | |
| mapboxgl: any; | |
| } | |
| } | |
| // Get Mapbox access token from environment variables | |
| const MAPBOX_ACCESS_TOKEN = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN; | |
| if (!MAPBOX_ACCESS_TOKEN) { | |
| throw new Error( | |
| "VITE_MAPBOX_ACCESS_TOKEN environment variable is required. Please check your .env file." | |
| ); | |
| } | |
| function createMapWithMonkeyPatch(container: HTMLElement) { | |
| // Store original constructors | |
| const originalHTMLElement = window.HTMLElement; | |
| const originalElement = window.Element; | |
| // Create a custom HTMLElement constructor that recognizes our element | |
| const CustomHTMLElement = function () {}; | |
| CustomHTMLElement.prototype = originalHTMLElement.prototype; | |
| // Override the instanceof check by changing the element's prototype chain | |
| Object.setPrototypeOf(container, CustomHTMLElement.prototype); | |
| // Temporarily replace global constructors | |
| (window as any).HTMLElement = CustomHTMLElement; | |
| (window as any).Element = CustomHTMLElement; | |
| try { | |
| // Create the map - now instanceof checks should pass | |
| const map = new window.mapboxgl.Map({ | |
| container: container, | |
| accessToken: MAPBOX_ACCESS_TOKEN, | |
| style: "mapbox://styles/mapbox/streets-v11", | |
| center: [-100, 40], | |
| zoom: 3.5, | |
| }); | |
| return map; | |
| } finally { | |
| // Restore original constructors to avoid side effects | |
| (window as any).HTMLElement = originalHTMLElement; | |
| (window as any).Element = originalElement; | |
| } | |
| } | |
| const MapComponent: React.FC = () => { | |
| const mapRef = React.useRef<any>(null); | |
| const containerRef = React.useRef<HTMLDivElement>(null); | |
| useEffect(() => { | |
| if (containerRef.current && window.mapboxgl) { | |
| try { | |
| const map = createMapWithMonkeyPatch(containerRef.current); | |
| mapRef.current = map; | |
| } catch (error) { | |
| console.error("Error creating Mapbox map:", error); | |
| } | |
| } | |
| return () => { | |
| if (mapRef.current) { | |
| mapRef.current.remove(); | |
| mapRef.current = null; | |
| } | |
| }; | |
| }, []); | |
| return ( | |
| <div | |
| ref={containerRef} | |
| id="map" | |
| style={{ width: "100%", height: "500px" }} | |
| /> | |
| ); | |
| }; | |
| export default class MapLayerPlugin extends ReactPlugin<{}, {}> { | |
| pluginProperties() { | |
| return {}; | |
| } | |
| component(): React.ReactNode { | |
| return <MapComponent />; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment