diff --git a/package-lock.json b/package-lock.json index 1c34fa1c3..534053b61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25841,7 +25841,7 @@ }, "packages/map-template": { "name": "@mapsindoors/map-template", - "version": "1.96.18", + "version": "1.96.19", "dependencies": { "@mapsindoors/components": "*", "@mapsindoors/css": "^3.0.0", diff --git a/packages/map-template/CHANGELOG.md b/packages/map-template/CHANGELOG.md index e295f7cf0..3d776cac6 100644 --- a/packages/map-template/CHANGELOG.md +++ b/packages/map-template/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.96.20] - 2026-03-27 + +### Fixed + +- An issue when expanded Floor Selector would overlap Zoom Controls and Full Screen button. +- Added a new custom hook `useFloorSelectorToggleObserver.js`. +- Added newly created observer to MapControls.jsx +- `minZoom` can be now configurable via App Config object, otherwise it defaults to 10. +- Upgraded to Web SDK 4.57.0. + ## [1.96.19] - 2026-03-26 ### Fixed diff --git a/packages/map-template/index.html b/packages/map-template/index.html index 3920a45de..235749219 100644 --- a/packages/map-template/index.html +++ b/packages/map-template/index.html @@ -6,8 +6,8 @@ MapsIndoors Web - diff --git a/packages/map-template/src/components/MIMap/MapControls/MapControls.jsx b/packages/map-template/src/components/MIMap/MapControls/MapControls.jsx index f1f867d8d..799e08c62 100644 --- a/packages/map-template/src/components/MIMap/MapControls/MapControls.jsx +++ b/packages/map-template/src/components/MIMap/MapControls/MapControls.jsx @@ -1,7 +1,8 @@ -import { useEffect, useRef, useCallback, useMemo, memo } from 'react'; +import { useEffect, useRef, useState, useCallback, useMemo, memo } from 'react'; import PropTypes from 'prop-types'; import './MapControls.scss'; import { useIsDesktop } from '../../../hooks/useIsDesktop'; +import { useFloorSelectorToggleObserver } from '../../../hooks/useFloorSelectorToggleObserver'; import CustomPositionProvider from '../../../utils/CustomPositionProvider'; import MapZoomControl from '../MapZoomControl/MapZoomControl'; import TextSizeButton from '../TextSizeButton/TextSizeButton'; @@ -60,6 +61,26 @@ function MapControls({ mapType, mapsIndoorsInstance, mapInstance, onPositionCont const isDesktop = useIsDesktop(); const floorSelectorRef = useRef(null); const positionButtonRef = useRef(null); + const bottomControlsRef = useRef(null); + const [isFloorSelectorExpanded, setIsFloorSelectorExpanded] = useState(false); + + // Measures whether the expanded floor selector overlaps the bottom controls. + // Uses only refs and stable setters so the dependency array can stay empty. + const measureOverlap = useCallback(() => { + const bottomControls = bottomControlsRef.current; + const floorSelector = floorSelectorRef.current; + + if (!bottomControls || !floorSelector) { + setIsFloorSelectorExpanded(false); + return; + } + + const selectorEl = floorSelector.querySelector('.mi-floor-selector') || floorSelector; + const selectorRect = selectorEl.getBoundingClientRect(); + const controlsRect = bottomControls.getBoundingClientRect(); + + setIsFloorSelectorExpanded(selectorRect.bottom > controlsRect.top); + }, []); // Helper function to check if an element should be rendered const shouldRenderElement = useCallback((elementName) => { @@ -210,6 +231,34 @@ function MapControls({ mapType, mapsIndoorsInstance, mapInstance, onPositionCont }); }, [excludedElements, shouldRenderElement, isDesktop]); + const isFloorSelectorOpenRef = useFloorSelectorToggleObserver({ + floorSelectorRef, + setIsFloorSelectorExpanded, + measureOverlap, + mapsIndoorsInstance, + mapInstance + }); + + // Recompute overlap on viewport resize, orientation change, and layout transitions. + useEffect(() => { + const onViewportChange = () => { + if (isFloorSelectorOpenRef.current) { + measureOverlap(); + } + }; + + window.addEventListener('resize', onViewportChange); + window.addEventListener('orientationchange', onViewportChange); + + // Re-check immediately after a layout switch while the selector is already open + onViewportChange(); + + return () => { + window.removeEventListener('resize', onViewportChange); + window.removeEventListener('orientationchange', onViewportChange); + }; + }, [isDesktop, isKiosk, measureOverlap]); + if (isKiosk) { if (enableAccessibilityKioskControls) { return ( @@ -274,7 +323,7 @@ function MapControls({ mapType, mapsIndoorsInstance, mapInstance, onPositionCont {/* Bottom right desktop controls */} -
+
{shouldRenderElement('zoomControls') && ( )} diff --git a/packages/map-template/src/components/MIMap/MapControls/MapControls.scss b/packages/map-template/src/components/MIMap/MapControls/MapControls.scss index 68b30d4a6..1c630c9bf 100644 --- a/packages/map-template/src/components/MIMap/MapControls/MapControls.scss +++ b/packages/map-template/src/components/MIMap/MapControls/MapControls.scss @@ -39,6 +39,12 @@ right: calc(1 * var(--spacing-medium)); } +// Hide bottom controls when the floor selector is expanded to prevent occlusion +.map-controls-container--floor-selector-open { + visibility: hidden; + pointer-events: none; +} + // Mobile layout, two columns aligned to the top left and right .mobile-column { @extend %control-container; diff --git a/packages/map-template/src/components/MapTemplate/MapTemplate.jsx b/packages/map-template/src/components/MapTemplate/MapTemplate.jsx index fd802b95e..d5b7c1d93 100644 --- a/packages/map-template/src/components/MapTemplate/MapTemplate.jsx +++ b/packages/map-template/src/components/MapTemplate/MapTemplate.jsx @@ -240,8 +240,8 @@ function MapTemplate({ apiKey, gmApiKey, mapboxAccessToken, venue, locationId, p const miSdkApiTag = document.createElement('script'); miSdkApiTag.setAttribute('type', 'text/javascript'); // Remember to update the root index.html with the same version / integrity - miSdkApiTag.setAttribute('src', 'https://app.mapsindoors.com/mapsindoors/js/sdk/4.56.2/mapsindoors-4.56.2.js.gz'); - miSdkApiTag.setAttribute('integrity', 'sha384-AbC6/Ti9R/Fs5vC9Vd+dXtQvBOSMbw7PQ1+xyaL3W5et4ZuNiP1K0GdmoDZvve7G'); + miSdkApiTag.setAttribute('src', 'https://app.mapsindoors.com/mapsindoors/js/sdk/4.57.0/mapsindoors-4.57.0.js.gz'); + miSdkApiTag.setAttribute('integrity', 'sha384-Hxm9bx6t+2luBNdH7oGaUAgcZH+FMgbKfltKDlJYJsGbFnXcWH0bm6ZigXLgFmQg'); miSdkApiTag.setAttribute('crossorigin', 'anonymous'); document.body.appendChild(miSdkApiTag); miSdkApiTag.onload = () => { @@ -524,7 +524,7 @@ function MapTemplate({ apiKey, gmApiKey, mapboxAccessToken, venue, locationId, p : appConfig?.appSettings?.showMapMarkers), miTransitionLevel: miTransitionLevel, // If ignoreViewportBounds is true, we would like to see the map at the minimum zoom level (World Map). - minZoom: appConfig?.appSettings?.ignoreViewportBounds ? 1 : ZoomLevelValues.minZoom, + minZoom: appConfig?.appSettings?.minZoom ?? ZoomLevelValues.minZoom, mapboxMapStyle: appConfig?.appSettings?.mapboxMapStyle || mapboxMapStyle, // Boolean from the App Config comes as a string. We need to return clean boolean value based on that. enableFullScreenButton: diff --git a/packages/map-template/src/hooks/useFloorSelectorToggleObserver.js b/packages/map-template/src/hooks/useFloorSelectorToggleObserver.js new file mode 100644 index 000000000..42140dd36 --- /dev/null +++ b/packages/map-template/src/hooks/useFloorSelectorToggleObserver.js @@ -0,0 +1,58 @@ +import { useEffect, useRef } from 'react'; + +/** + * Observes the floor selector's toggle button class to detect open/close, + * then measures overlap after the expansion animation finishes. + * + * @param {Object} params + * @param {React.RefObject} params.floorSelectorRef - Ref to the floor selector element + * @param {Function} params.setIsFloorSelectorExpanded - State setter for floor selector expansion + * @param {Function} params.measureOverlap - Callback to measure overlap between floor selector and bottom controls + * @param {Object} params.mapsIndoorsInstance - MapsIndoors SDK instance (dependency trigger) + * @param {Object} params.mapInstance - Map instance (dependency trigger) + * @returns {React.RefObject} isFloorSelectorOpenRef - Ref tracking whether the floor selector is open + */ +export function useFloorSelectorToggleObserver({ floorSelectorRef, setIsFloorSelectorExpanded, measureOverlap, mapsIndoorsInstance, mapInstance }) { + const overlapTimerRef = useRef(null); + const isFloorSelectorOpenRef = useRef(false); + + useEffect(() => { + const floorSelector = floorSelectorRef.current; + if (!floorSelector) return; + + const onClassChange = () => { + if (overlapTimerRef.current) { + clearTimeout(overlapTimerRef.current); + } + + const button = floorSelector.querySelector('.mi-floor-selector__button'); + if (!button) return; + + const isOpen = button.classList.contains('mi-floor-selector__button--open'); + isFloorSelectorOpenRef.current = isOpen; + + if (!isOpen) { + setIsFloorSelectorExpanded(false); + return; + } + + overlapTimerRef.current = setTimeout(measureOverlap, 50); + }; + + const observer = new MutationObserver(onClassChange); + observer.observe(floorSelector, { + subtree: true, + attributes: true, + attributeFilter: ['class'] + }); + + return () => { + observer.disconnect(); + if (overlapTimerRef.current) { + clearTimeout(overlapTimerRef.current); + } + }; + }, [floorSelectorRef, setIsFloorSelectorExpanded, measureOverlap, mapsIndoorsInstance, mapInstance]); + + return isFloorSelectorOpenRef; +}