Add hover pins for clustered properties, disable grouping under 30 markers

- Add data-lat/data-lng attributes to MLS property cards
- Create temporary highlighted pin on card hover when marker is clustered
- Show individual pins (no grouping) when <= 30 properties in viewport
- Add markerLayer for unclustered markers to bypass client-side clustering
- Show loading spinner immediately on map move to abort image loads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hanson.xyz Dev
2025-12-17 02:31:23 -06:00
parent 8cd630593d
commit dfad0f57e6
4 changed files with 114 additions and 11 deletions
File diff suppressed because one or more lines are too long
@@ -65,7 +65,7 @@ $image_url = function_exists('mls_get_image_url') ? mls_get_image_url($listing_k
$has_image = !empty($image_url);
?>
<article id="property-<?php echo esc_attr($listing_key); ?>" data-property-id="<?php echo esc_attr($listing_key); ?>" class="property-card card mls-property">
<article id="property-<?php echo esc_attr($listing_key); ?>" data-property-id="<?php echo esc_attr($listing_key); ?>"<?php if ($property->latitude && $property->longitude) : ?> data-lat="<?php echo esc_attr($property->latitude); ?>" data-lng="<?php echo esc_attr($property->longitude); ?>"<?php endif; ?> class="property-card card mls-property">
<a href="<?php echo esc_url($property_url); ?>" class="property-card-link-overlay" aria-hidden="true" tabindex="-1"></a>
<div class="property-card-image<?php echo $has_image ? ' has-image is-loading' : ''; ?>"<?php if ($has_image) : ?> data-bg="<?php echo esc_url($image_url); ?>"<?php endif; ?>>
<?php if ($has_image) : ?>
@@ -140,9 +140,11 @@
densityLayer: null, // Layer group for density dots (zoom 1-11)
clusterLayer: null, // Layer group for server clusters (zoom 12-15)
markerCluster: null, // MarkerClusterGroup for individual markers (zoom 16+)
markerLayer: null, // Plain layer group for unclustered markers (low count)
selectedPropertyId: null,
isPinClickPan: false, // Flag: true when map pan is caused by pin click (don't clear selection)
hoveredPropertyId: null,
temporaryHoverMarker: null, // Temporary marker for clustered properties on hover
baseZIndex: 400,
currentFilters: {},
currentMode: null, // Track current visualization mode
@@ -173,6 +175,9 @@
// Create layer for server-side clusters (zoom 12-15)
this.clusterLayer = L.layerGroup().addTo(this.map);
// Create plain layer for unclustered markers (low count)
this.markerLayer = L.layerGroup().addTo(this.map);
// Create marker cluster group for individual markers (when zoomed in)
this.markerCluster = L.markerClusterGroup({
maxClusterRadius: 50,
@@ -287,7 +292,14 @@
this.densityLayer.clearLayers();
this.clusterLayer.clearLayers();
this.markerCluster.clearLayers();
this.markerLayer.clearLayers();
this.markers = {};
// Also remove temporary hover marker if present
if (this.temporaryHoverMarker) {
this.map.removeLayer(this.temporaryHoverMarker);
this.temporaryHoverMarker = null;
}
},
/**
@@ -477,8 +489,16 @@
}
});
// Bulk add markers for performance
this.markerCluster.addLayers(markersToAdd);
// Add markers to appropriate layer based on count
// Use plain layer for <= 30 markers (no client-side clustering)
// Use MarkerClusterGroup for > 30 markers (client-side clustering)
if (markersToAdd.length <= 30) {
markersToAdd.forEach(function(marker) {
self.markerLayer.addLayer(marker);
});
} else {
this.markerCluster.addLayers(markersToAdd);
}
// Reset the pin-click-pan flag now that markers are rendered
this.isPinClickPan = false;
@@ -622,7 +642,8 @@
var self = this;
$(document).on('mouseenter', '.property-card[data-property-id]', function() {
var propertyId = $(this).data('property-id');
var $card = $(this);
var propertyId = $card.data('property-id');
// Don't change if this is the selected (amber) card
if (propertyId === self.selectedPropertyId) {
@@ -630,8 +651,33 @@
}
self.hoveredPropertyId = propertyId;
self.setMarkerColor(propertyId, 'blue');
self.setMarkerZIndex(propertyId, 9000); // Blue below amber but above red
// Check if marker exists on map
if (self.markers[propertyId]) {
// Marker exists - highlight it
self.setMarkerColor(propertyId, 'blue');
self.setMarkerZIndex(propertyId, 9000); // Blue below amber but above red
} else {
// Marker is clustered - create temporary pin at property location
var lat = $card.data('lat');
var lng = $card.data('lng');
if (lat && lng && self.map) {
// Remove any existing temporary marker
if (self.temporaryHoverMarker) {
self.map.removeLayer(self.temporaryHoverMarker);
}
// Create temporary marker with blue color (highlighted)
self.temporaryHoverMarker = L.marker([lat, lng], {
icon: self.createIcon('blue'),
zIndexOffset: 15000 // Above everything else
});
// Add directly to map (not to cluster layer)
self.temporaryHoverMarker.addTo(self.map);
}
}
});
$(document).on('mouseleave', '.property-card[data-property-id]', function() {
@@ -646,8 +692,17 @@
self.hoveredPropertyId = null;
}
self.setMarkerColor(propertyId, 'red');
self.resetMarkerZIndex(propertyId);
// Remove temporary hover marker if it exists
if (self.temporaryHoverMarker) {
self.map.removeLayer(self.temporaryHoverMarker);
self.temporaryHoverMarker = null;
}
// Reset regular marker if it exists
if (self.markers[propertyId]) {
self.setMarkerColor(propertyId, 'red');
self.resetMarkerZIndex(propertyId);
}
});
},
@@ -919,6 +974,9 @@
this.clearPinSelection();
}
// Immediately show spinner and clear grid to abort image loading
this.$results.html('<div class="property-results-loading"><div class="spinner"></div></div>');
// Reset infinite scroll state before loading new content
if (InfiniteScrollState.isEnabled) {
InfiniteScroll.reset();