From 9d970efdbda578c12802502dc0008a7d6929b37d Mon Sep 17 00:00:00 2001 From: Kshitij Karandikar Date: Wed, 6 Aug 2025 13:03:45 +0200 Subject: [PATCH 1/3] fix: resolve mapbox tiles loading and auto-zoom timing issues - Upgrade leaflet from ^1.9.4 to 2.0.0-alpha to fix issues with map tiles not loading - Add getDimensions utility to handle widget dimensions locally - Fix auto-zoom timing in LeafletMap component with proper delay mechanism - Add maxZoom limit (15) to prevent over-zooming when markers are close together - Use pixel-based padding for consistent spacing around markers - Update imports to use local getDimensions utility instead of external dependency - Ensure consistent auto-zoom behavior for Mapbox, OpenStreet, and Here Maps providers Fixes auto-zoom firing too early before markers are properly rendered. --- .../pluggableWidgets/maps-web/package.json | 2 +- .../maps-web/src/components/GoogleMap.tsx | 2 +- .../maps-web/src/components/LeafletMap.tsx | 62 ++++++++++++++----- .../maps-web/src/utils/get-dimensions.ts | 38 ++++++++++++ .../maps-web/typings/shared.d.ts | 2 +- 5 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 packages/pluggableWidgets/maps-web/src/utils/get-dimensions.ts diff --git a/packages/pluggableWidgets/maps-web/package.json b/packages/pluggableWidgets/maps-web/package.json index e7c1550273..46f0140f54 100644 --- a/packages/pluggableWidgets/maps-web/package.json +++ b/packages/pluggableWidgets/maps-web/package.json @@ -46,7 +46,7 @@ "@vis.gl/react-google-maps": "^0.8.3", "classnames": "^2.5.1", "deep-equal": "^2.2.3", - "leaflet": "^1.9.4", + "leaflet": "2.0.0-alpha", "react-leaflet": "^4.2.1" }, "devDependencies": { diff --git a/packages/pluggableWidgets/maps-web/src/components/GoogleMap.tsx b/packages/pluggableWidgets/maps-web/src/components/GoogleMap.tsx index 9109cf27b4..6e92c99d54 100644 --- a/packages/pluggableWidgets/maps-web/src/components/GoogleMap.tsx +++ b/packages/pluggableWidgets/maps-web/src/components/GoogleMap.tsx @@ -13,7 +13,7 @@ import { } from "@vis.gl/react-google-maps"; import { Marker, SharedProps } from "../../typings/shared"; import { translateZoom } from "../utils/zoom"; -import { getDimensions } from "@mendix/widget-plugin-platform/utils/get-dimensions"; +import { getDimensions } from "../utils/get-dimensions"; export interface GoogleMapsProps extends SharedProps { mapId: string; diff --git a/packages/pluggableWidgets/maps-web/src/components/LeafletMap.tsx b/packages/pluggableWidgets/maps-web/src/components/LeafletMap.tsx index f7823358d3..f6f10ac58a 100644 --- a/packages/pluggableWidgets/maps-web/src/components/LeafletMap.tsx +++ b/packages/pluggableWidgets/maps-web/src/components/LeafletMap.tsx @@ -1,13 +1,16 @@ -import { createElement, ReactElement } from "react"; +import { createElement, ReactElement, useEffect } from "react"; import { MapContainer, Marker as MarkerComponent, Popup, TileLayer, useMap } from "react-leaflet"; import classNames from "classnames"; -import { getDimensions } from "@mendix/widget-plugin-platform/utils/get-dimensions"; +import { getDimensions } from "../utils/get-dimensions"; import { SharedProps } from "../../typings/shared"; import { MapProviderEnum } from "../../typings/MapsProps"; import { translateZoom } from "../utils/zoom"; -import { latLngBounds, Icon as LeafletIcon, DivIcon } from "leaflet"; +import { Icon as LeafletIcon, DivIcon } from "leaflet"; import { baseMapLayer } from "../utils/leaflet"; +// Global variable for marker render delay +const MARKER_RENDER_DELAY = 100; + export interface LeafletProps extends SharedProps { mapProvider: MapProviderEnum; attributionControl: boolean; @@ -36,20 +39,44 @@ function SetBoundsComponent(props: Pick !!m) - .map(m => [m.latitude, m.longitude]) - ); + useEffect(() => { + if (map) { + // Add a small delay to ensure markers are rendered + const timer = setTimeout(() => { + const allMarkers = locations.concat(currentLocation ? [currentLocation] : []).filter(m => !!m); + + if (allMarkers.length > 0) { + const lats = allMarkers.map(m => m.latitude); + const lngs = allMarkers.map(m => m.longitude); + + const southWest = [Math.min(...lats), Math.min(...lngs)] as [number, number]; + const northEast = [Math.max(...lats), Math.max(...lngs)] as [number, number]; - if (bounds.isValid()) { - if (autoZoom) { - map.flyToBounds(bounds, { padding: [0.5, 0.5], animate: false }).invalidateSize(); - } else { - map.panTo(bounds.getCenter(), { animate: false }); + if (autoZoom) { + // Use more conservative options for flyToBounds + const flyOptions = { + padding: [20, 20] as [number, number], // Use pixel padding + animate: false, + maxZoom: 15 // Limit maximum zoom to prevent over-zooming + }; + + map.flyToBounds([southWest, northEast], flyOptions); + + // Force invalidate size after bounds are set + setTimeout(() => { + map.invalidateSize(); + }, 50); + } else { + const centerLat = (southWest[0] + northEast[0]) / 2; + const centerLng = (southWest[1] + northEast[1]) / 2; + map.panTo([centerLat, centerLng], { animate: false }); + } + } + }, MARKER_RENDER_DELAY); + + return () => clearTimeout(timer); } - } + }, [map, locations, currentLocation, autoZoom]); return null; } @@ -71,6 +98,9 @@ export function LeafletMap(props: LeafletProps): ReactElement { optionDrag: dragging } = props; + // Use a lower initial zoom when autoZoom is enabled to prevent conflicts + const initialZoom = autoZoom ? Math.min(translateZoom("city"), zoom) : zoom; + return (
@@ -82,7 +112,7 @@ export function LeafletMap(props: LeafletProps): ReactElement { maxZoom={18} minZoom={1} scrollWheelZoom={scrollWheelZoom} - zoom={autoZoom ? translateZoom("city") : zoom} + zoom={initialZoom} zoomControl={zoomControl} > diff --git a/packages/pluggableWidgets/maps-web/src/utils/get-dimensions.ts b/packages/pluggableWidgets/maps-web/src/utils/get-dimensions.ts new file mode 100644 index 0000000000..739f000f46 --- /dev/null +++ b/packages/pluggableWidgets/maps-web/src/utils/get-dimensions.ts @@ -0,0 +1,38 @@ +import { CSSProperties } from "react"; + +export type WidthUnitEnum = "percentage" | "pixels"; + +export type HeightUnitEnum = "percentageOfWidth" | "pixels" | "percentageOfParent"; + +export interface Dimensions { + widthUnit: WidthUnitEnum; + width: number | null; + heightUnit: HeightUnitEnum; + height: number | null; +} + +export function getDimensions(props: T): CSSProperties { + const style: CSSProperties = {}; + + if (props.width) { + style.width = props.widthUnit === "percentage" ? `${props.width}%` : `${props.width}px`; + } + + if (props.height) { + if (props.heightUnit === "percentageOfWidth" && props.width) { + const ratio = (props.height / 100) * props.width; + if (props.widthUnit === "percentage") { + style.height = "auto"; + style.paddingBottom = `${ratio}%`; + } else { + style.height = `${ratio}px`; + } + } else if (props.heightUnit === "pixels") { + style.height = `${props.height}px`; + } else if (props.heightUnit === "percentageOfParent") { + style.height = `${props.height}%`; + } + } + + return style; +} diff --git a/packages/pluggableWidgets/maps-web/typings/shared.d.ts b/packages/pluggableWidgets/maps-web/typings/shared.d.ts index 9c0c4eeeb2..bd3dbfa439 100644 --- a/packages/pluggableWidgets/maps-web/typings/shared.d.ts +++ b/packages/pluggableWidgets/maps-web/typings/shared.d.ts @@ -1,4 +1,4 @@ -import { Dimensions } from "@mendix/widget-plugin-platform/utils/get-dimensions"; +import { Dimensions } from "../src/utils/get-dimensions"; import { CSSProperties } from "react"; export interface ModeledMarker { address?: string; From 3598e939f68d2f2ab5e8a482b996a6db701ae499 Mon Sep 17 00:00:00 2001 From: Kshitij Karandikar Date: Wed, 6 Aug 2025 13:23:50 +0200 Subject: [PATCH 2/3] test: add ResizeObserver polyfill and update snapshots for Leaflet 2.0.0-alpha - Add Jest setup file with ResizeObserver polyfill to fix test compatibility - Update Jest configuration to use the new setup file - Update all LeafletMap test snapshots to match new Leaflet 2.0.0-alpha structure - Ensure all tests pass with the new Leaflet version dependency --- .../pluggableWidgets/maps-web/jest.config.js | 3 +- .../pluggableWidgets/maps-web/jest.setup.js | 8 + .../__snapshots__/LeafletMap.spec.tsx.snap | 154 ++++++++++++------ 3 files changed, 116 insertions(+), 49 deletions(-) create mode 100644 packages/pluggableWidgets/maps-web/jest.setup.js diff --git a/packages/pluggableWidgets/maps-web/jest.config.js b/packages/pluggableWidgets/maps-web/jest.config.js index 6c49746721..ad93d48df4 100644 --- a/packages/pluggableWidgets/maps-web/jest.config.js +++ b/packages/pluggableWidgets/maps-web/jest.config.js @@ -1,4 +1,5 @@ module.exports = { ...require("@mendix/pluggable-widgets-tools/test-config/jest.enzyme-free.config.js"), - transformIgnorePatterns: ["node_modules/(?!(.*leaflet.*))"] + transformIgnorePatterns: ["node_modules/(?!(.*leaflet.*))"], + setupFilesAfterEnv: ["../jest.setup.js"] }; diff --git a/packages/pluggableWidgets/maps-web/jest.setup.js b/packages/pluggableWidgets/maps-web/jest.setup.js new file mode 100644 index 0000000000..082fe5288a --- /dev/null +++ b/packages/pluggableWidgets/maps-web/jest.setup.js @@ -0,0 +1,8 @@ +// Jest setup file to add polyfills for testing + +// Polyfill for ResizeObserver which is required by Leaflet 2.0.0-alpha +global.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn() +})); diff --git a/packages/pluggableWidgets/maps-web/src/components/__tests__/__snapshots__/LeafletMap.spec.tsx.snap b/packages/pluggableWidgets/maps-web/src/components/__tests__/__snapshots__/LeafletMap.spec.tsx.snap index 3ae4c90fa0..5f7480e898 100644 --- a/packages/pluggableWidgets/maps-web/src/components/__tests__/__snapshots__/LeafletMap.spec.tsx.snap +++ b/packages/pluggableWidgets/maps-web/src/components/__tests__/__snapshots__/LeafletMap.spec.tsx.snap @@ -10,13 +10,13 @@ exports[`Leaflet maps renders a map with HERE maps as provider 1`] = ` class="widget-leaflet-maps-wrapper" >
@@ -53,6 +53,10 @@ exports[`Leaflet maps renders a map with HERE maps as provider 1`] = `
+
@@ -162,6 +166,10 @@ exports[`Leaflet maps renders a map with MapBox maps as provider 1`] = `
+
@@ -271,6 +279,10 @@ exports[`Leaflet maps renders a map with attribution 1`] = `
+
+ Leaflet @@ -361,13 +395,13 @@ exports[`Leaflet maps renders a map with current location 1`] = ` class="widget-leaflet-maps-wrapper" >
@@ -399,9 +433,9 @@ exports[`Leaflet maps renders a map with current location 1`] = ` class="leaflet-pane leaflet-marker-pane" >
+
@@ -521,9 +559,9 @@ exports[`Leaflet maps renders a map with markers 1`] = ` class="leaflet-pane leaflet-marker-pane" >
@@ -534,9 +572,9 @@ exports[`Leaflet maps renders a map with markers 1`] = ` />
@@ -553,6 +591,10 @@ exports[`Leaflet maps renders a map with markers 1`] = `
+
@@ -662,6 +704,10 @@ exports[`Leaflet maps renders a map with percentage of parent units renders the
+
@@ -771,6 +817,10 @@ exports[`Leaflet maps renders a map with percentage of width and height units re
+
@@ -880,6 +930,10 @@ exports[`Leaflet maps renders a map with pixels renders structure correctly 1`]
+
@@ -989,6 +1043,10 @@ exports[`Leaflet maps renders a map with right structure 1`] = `
+
Date: Wed, 6 Aug 2025 13:34:43 +0200 Subject: [PATCH 3/3] refactor: use original getDimensions from @mendix/widget-plugin-platform - Change imports back to @mendix/widget-plugin-platform/utils/get-dimensions - Remove local duplicate get-dimensions.ts utility file - Fix module resolution by building widget-plugin-platform package - Maintain all functionality while using shared utility instead of local copy --- .../maps-web/src/components/GoogleMap.tsx | 2 +- .../maps-web/src/components/LeafletMap.tsx | 2 +- .../maps-web/src/utils/get-dimensions.ts | 38 ------------------- .../maps-web/typings/shared.d.ts | 2 +- 4 files changed, 3 insertions(+), 41 deletions(-) delete mode 100644 packages/pluggableWidgets/maps-web/src/utils/get-dimensions.ts diff --git a/packages/pluggableWidgets/maps-web/src/components/GoogleMap.tsx b/packages/pluggableWidgets/maps-web/src/components/GoogleMap.tsx index 6e92c99d54..9109cf27b4 100644 --- a/packages/pluggableWidgets/maps-web/src/components/GoogleMap.tsx +++ b/packages/pluggableWidgets/maps-web/src/components/GoogleMap.tsx @@ -13,7 +13,7 @@ import { } from "@vis.gl/react-google-maps"; import { Marker, SharedProps } from "../../typings/shared"; import { translateZoom } from "../utils/zoom"; -import { getDimensions } from "../utils/get-dimensions"; +import { getDimensions } from "@mendix/widget-plugin-platform/utils/get-dimensions"; export interface GoogleMapsProps extends SharedProps { mapId: string; diff --git a/packages/pluggableWidgets/maps-web/src/components/LeafletMap.tsx b/packages/pluggableWidgets/maps-web/src/components/LeafletMap.tsx index f6f10ac58a..56e4d2aa37 100644 --- a/packages/pluggableWidgets/maps-web/src/components/LeafletMap.tsx +++ b/packages/pluggableWidgets/maps-web/src/components/LeafletMap.tsx @@ -1,7 +1,7 @@ import { createElement, ReactElement, useEffect } from "react"; import { MapContainer, Marker as MarkerComponent, Popup, TileLayer, useMap } from "react-leaflet"; import classNames from "classnames"; -import { getDimensions } from "../utils/get-dimensions"; +import { getDimensions } from "@mendix/widget-plugin-platform/utils/get-dimensions"; import { SharedProps } from "../../typings/shared"; import { MapProviderEnum } from "../../typings/MapsProps"; import { translateZoom } from "../utils/zoom"; diff --git a/packages/pluggableWidgets/maps-web/src/utils/get-dimensions.ts b/packages/pluggableWidgets/maps-web/src/utils/get-dimensions.ts deleted file mode 100644 index 739f000f46..0000000000 --- a/packages/pluggableWidgets/maps-web/src/utils/get-dimensions.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { CSSProperties } from "react"; - -export type WidthUnitEnum = "percentage" | "pixels"; - -export type HeightUnitEnum = "percentageOfWidth" | "pixels" | "percentageOfParent"; - -export interface Dimensions { - widthUnit: WidthUnitEnum; - width: number | null; - heightUnit: HeightUnitEnum; - height: number | null; -} - -export function getDimensions(props: T): CSSProperties { - const style: CSSProperties = {}; - - if (props.width) { - style.width = props.widthUnit === "percentage" ? `${props.width}%` : `${props.width}px`; - } - - if (props.height) { - if (props.heightUnit === "percentageOfWidth" && props.width) { - const ratio = (props.height / 100) * props.width; - if (props.widthUnit === "percentage") { - style.height = "auto"; - style.paddingBottom = `${ratio}%`; - } else { - style.height = `${ratio}px`; - } - } else if (props.heightUnit === "pixels") { - style.height = `${props.height}px`; - } else if (props.heightUnit === "percentageOfParent") { - style.height = `${props.height}%`; - } - } - - return style; -} diff --git a/packages/pluggableWidgets/maps-web/typings/shared.d.ts b/packages/pluggableWidgets/maps-web/typings/shared.d.ts index bd3dbfa439..9c0c4eeeb2 100644 --- a/packages/pluggableWidgets/maps-web/typings/shared.d.ts +++ b/packages/pluggableWidgets/maps-web/typings/shared.d.ts @@ -1,4 +1,4 @@ -import { Dimensions } from "../src/utils/get-dimensions"; +import { Dimensions } from "@mendix/widget-plugin-platform/utils/get-dimensions"; import { CSSProperties } from "react"; export interface ModeledMarker { address?: string;