From d5bfb66eeeaf8f608d09864575cc8e7dca31df16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Mummert?= Date: Thu, 26 Feb 2026 21:08:37 +0100 Subject: [PATCH] Fix spiderfy reliability after event toggle --- contao/templates/frontend/event_map.html.twig | 2 +- public/assets/map-module.js | 115 +++++++++++------- 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/contao/templates/frontend/event_map.html.twig b/contao/templates/frontend/event_map.html.twig index 8608892..8b7f177 100644 --- a/contao/templates/frontend/event_map.html.twig +++ b/contao/templates/frontend/event_map.html.twig @@ -107,4 +107,4 @@ - + diff --git a/public/assets/map-module.js b/public/assets/map-module.js index cabdcdc..2208f02 100644 --- a/public/assets/map-module.js +++ b/public/assets/map-module.js @@ -532,6 +532,7 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event const eventToggleButton = wrapper.querySelector('[data-map-event-toggle="1"]'); const canFilterByTag = tagButtons.length > 0; const canFilterByEvents = !!eventToggleButton; + const canToggleEvents = !!(eventToggleButton && eventLayerManager); const setEventButtonState = (isActive) => { if (!eventToggleButton) { @@ -549,6 +550,26 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event let activeTagId = null; let eventsOnly = false; + const updateExclusiveButtonAccessibility = () => { + tagButtons.forEach((button) => { + const isActive = button.getAttribute('aria-pressed') === 'true'; + button.setAttribute('aria-disabled', isActive ? 'true' : 'false'); + }); + + if (!eventToggleButton) { + return; + } + + if (!canToggleEvents) { + eventToggleButton.setAttribute('aria-disabled', 'true'); + + return; + } + + const isEventActive = eventToggleButton.getAttribute('aria-pressed') === 'true'; + eventToggleButton.setAttribute('aria-disabled', isEventActive ? 'true' : 'false'); + }; + const applyFilter = () => { if (eventsOnly) { organizationMarkerManager.setVisible(false); @@ -581,14 +602,17 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event tagButtons.forEach((button) => { button.addEventListener('click', () => { + if (button.getAttribute('aria-disabled') === 'true') { + return; + } + const clickedTagId = String(button.dataset.mapTagFilter || '').trim(); if (!/^\d+$/.test(clickedTagId)) { return; } - const isActive = button.getAttribute('aria-pressed') === 'true'; - activeTagId = isActive ? null : clickedTagId; + activeTagId = clickedTagId; eventsOnly = false; tagButtons.forEach((otherButton) => { @@ -597,6 +621,7 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event }); setEventButtonState(false); + updateExclusiveButtonAccessibility(); applyFilter(); }); }); @@ -628,12 +653,16 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event if (eventToggleButton && eventLayerManager) { eventToggleButton.addEventListener('click', () => { - const isActive = eventToggleButton.getAttribute('aria-pressed') === 'true'; - eventsOnly = !isActive; + if (eventToggleButton.getAttribute('aria-disabled') === 'true') { + return; + } + + eventsOnly = true; activeTagId = null; tagButtons.forEach((button) => setButtonState(button, false)); setEventButtonState(eventsOnly); + updateExclusiveButtonAccessibility(); applyFilter(); }); } else if (eventToggleButton) { @@ -711,9 +740,11 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event setButtonState(button, null !== activeTagId && tagId === activeTagId); }); setEventButtonState(eventsOnly); + updateExclusiveButtonAccessibility(); }; applyInitialDisplayMode(); + updateExclusiveButtonAccessibility(); applyFilter(); applyMapStyleMode(); }; @@ -748,6 +779,7 @@ const initLocationLayers = (map, locationItems, markerImageId) => { }, paint: { 'icon-opacity': 1, + 'icon-translate': [0, 4], }, }); @@ -831,19 +863,9 @@ const initLocationLayers = (map, locationItems, markerImageId) => { const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMarkerImageId) => { const sourceId = 'eventmanager-events-source'; const clusterLayerId = EVENT_CLUSTER_LAYER_ID; - const clusterSpiderfyLayerId = EVENT_CLUSTER_SPIDERFY_LAYER_ID; const unclusteredLayerId = EVENT_UNCLUSTERED_LAYER_ID; - const spiderfyHitAreaImageId = 'eventmanager-spiderfy-hit-area'; const features = eventItems.map(toFeature); - if (!map.hasImage(spiderfyHitAreaImageId)) { - map.addImage(spiderfyHitAreaImageId, { - width: 1, - height: 1, - data: new Uint8Array([0, 0, 0, 0]), - }); - } - map.addSource(sourceId, { type: 'geojson', data: { @@ -875,25 +897,10 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark }, paint: { 'icon-opacity': 1, + 'icon-translate': [0, 4], 'text-color': '#FFFFFF', 'text-opacity': 1, - }, - }); - - map.addLayer({ - id: clusterSpiderfyLayerId, - type: 'symbol', - source: sourceId, - filter: ['has', 'point_count'], - layout: { - 'icon-image': spiderfyHitAreaImageId, - 'icon-size': 36, - 'icon-anchor': 'bottom', - 'icon-offset': [0, -1.0], - 'icon-allow-overlap': true, - }, - paint: { - 'icon-opacity': 0, + 'text-translate': [0, 4], }, }); @@ -911,6 +918,7 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark }, paint: { 'icon-opacity': 1, + 'icon-translate': [0, 4], }, }); @@ -1016,7 +1024,7 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark }, spiderLeavesPaint: { 'icon-opacity': 1, - 'icon-translate': [0, 8], + 'icon-translate': [0, 9], }, onLeafClick: (feature, spiderEvent) => { const clickCoordinates = spiderEvent?.lngLat @@ -1048,14 +1056,6 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark map.getCanvas().style.cursor = ''; }); - const setLayerLayoutVisibility = (layerId, isVisible) => { - if (!map.getLayer(layerId)) { - return; - } - - map.setLayoutProperty(layerId, 'visibility', isVisible ? 'visible' : 'none'); - }; - const fadeLayerOpacity = (layerId, property, value, duration = EVENT_FADE_DURATION_MS) => { if (!map.getLayer(layerId)) { return; @@ -1067,14 +1067,31 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark const eventLayerIds = [ clusterLayerId, - clusterSpiderfyLayerId, unclusteredLayerId, ]; + const clusterFilter = ['has', 'point_count']; + const unclusteredFilter = ['!', ['has', 'point_count']]; + const hiddenFilter = ['has', '__eventmanager_hidden__']; + let hideLayersTimeoutId = null; + + const setLayerFilter = (layerId, filter) => { + if (!map.getLayer(layerId)) { + return; + } + + map.setFilter(layerId, filter); + }; return { setVisible: (isVisible) => { if (isVisible) { - eventLayerIds.forEach((layerId) => setLayerLayoutVisibility(layerId, true)); + if (null !== hideLayersTimeoutId) { + window.clearTimeout(hideLayersTimeoutId); + hideLayersTimeoutId = null; + } + + setLayerFilter(clusterLayerId, clusterFilter); + setLayerFilter(unclusteredLayerId, unclusteredFilter); fadeLayerOpacity(clusterLayerId, 'icon-opacity', 1); fadeLayerOpacity(clusterLayerId, 'text-opacity', 1); @@ -1083,16 +1100,22 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark return; } - if (spiderfyInstance && typeof spiderfyInstance.unspiderfyAll === 'function') { - spiderfyInstance.unspiderfyAll(); + if (spiderfyInstance) { + if (typeof spiderfyInstance._clearSpiderifiedCluster === 'function') { + spiderfyInstance._clearSpiderifiedCluster(); + } else if (typeof spiderfyInstance.unspiderfyAll === 'function') { + spiderfyInstance.unspiderfyAll(); + } } fadeLayerOpacity(clusterLayerId, 'icon-opacity', 0); fadeLayerOpacity(clusterLayerId, 'text-opacity', 0); fadeLayerOpacity(unclusteredLayerId, 'icon-opacity', 0); - window.setTimeout(() => { - eventLayerIds.forEach((layerId) => setLayerLayoutVisibility(layerId, false)); + hideLayersTimeoutId = window.setTimeout(() => { + setLayerFilter(clusterLayerId, hiddenFilter); + setLayerFilter(unclusteredLayerId, hiddenFilter); + hideLayersTimeoutId = null; }, EVENT_FADE_DURATION_MS + 10); }, };