Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/map-template/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ 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.

## [1.96.19] - 2026-03-26

### Fixed
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -274,7 +323,7 @@ function MapControls({ mapType, mapsIndoorsInstance, mapInstance, onPositionCont
</div>

{/* Bottom right desktop controls */}
<div className="map-controls-container desktop bottom-right">
<div ref={bottomControlsRef} className={`map-controls-container desktop bottom-right ${isFloorSelectorExpanded ? 'map-controls-container--floor-selector-open' : ''}`}>
{shouldRenderElement('zoomControls') && (
<MapZoomControl mapType={mapType} mapInstance={mapInstance} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
58 changes: 58 additions & 0 deletions packages/map-template/src/hooks/useFloorSelectorToggleObserver.js
Original file line number Diff line number Diff line change
@@ -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<boolean>} 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;
}