Add sticky filter improvements and unified filter input styling

- Change sticky filter visibility check to use reset button position
- Add reset button to sticky filter (centered, below filter grid)
- Reset filters now resets map to initial position if no results
- Unify select/input styling: same height, black background, consistent borders
This commit is contained in:
Hanson.xyz Dev
2025-12-17 17:36:38 -06:00
parent 564d556a8c
commit b8d9c8aee7
5 changed files with 150 additions and 45 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -95,5 +95,8 @@ $mls_cities = homeproz_get_mls_cities(50);
<input type="text" name="zip" class="filter-input" placeholder="Zip" value="<?php echo esc_attr($current_zip); ?>" maxlength="10" pattern="[0-9\-]*" aria-label="Zip Code"> <input type="text" name="zip" class="filter-input" placeholder="Zip" value="<?php echo esc_attr($current_zip); ?>" maxlength="10" pattern="[0-9\-]*" aria-label="Zip Code">
</div> </div>
</div> </div>
<div class="filters-sticky-reset">
<a href="<?php echo esc_url(get_post_type_archive_link('property')); ?>" class="btn btn-secondary filters-reset">Reset</a>
</div>
</form> </form>
</div> </div>
@@ -281,6 +281,8 @@
baseZIndex: 400, baseZIndex: 400,
currentFilters: {}, currentFilters: {},
currentMode: null, // Track current visualization mode currentMode: null, // Track current visualization mode
initialCenter: [45.0, -93.5], // Initial map center (Minnesota)
initialZoom: 7, // Initial zoom level
/** /**
* Initialize the map * Initialize the map
@@ -683,6 +685,15 @@
this.loadClusters(); this.loadClusters();
}, },
/**
* Reset map to initial position (Minnesota overview)
*/
resetToInitialPosition: function() {
if (this.map) {
this.map.setView(this.initialCenter, this.initialZoom);
}
},
/** /**
* Format number with commas * Format number with commas
*/ */
@@ -895,7 +906,9 @@
$stickyFilter: null, $stickyFilter: null,
$mainForm: null, $mainForm: null,
$stickyForm: null, $stickyForm: null,
observer: null, $resetButton: null,
$masthead: null,
scrollTimeout: null,
isVisible: false, isVisible: false,
/** /**
@@ -911,38 +924,49 @@
this.$stickyFilter = $('#property-filters-sticky'); this.$stickyFilter = $('#property-filters-sticky');
this.$mainForm = this.$mainFilter.find('.filters-form'); this.$mainForm = this.$mainFilter.find('.filters-form');
this.$stickyForm = this.$stickyFilter.find('.filters-form-sticky'); this.$stickyForm = this.$stickyFilter.find('.filters-form-sticky');
this.$resetButton = this.$mainFilter.find('.filter-item-button .btn');
this.$masthead = $('#masthead');
if (!this.$mainFilter.length || !this.$stickyFilter.length) { if (!this.$mainFilter.length || !this.$stickyFilter.length || !this.$resetButton.length) {
return; return;
} }
this.setupObserver(); this.setupScrollHandler();
this.bindEvents(); this.bindEvents();
// Check initial state
this.checkVisibility();
}, },
/** /**
* Setup IntersectionObserver on main filter * Setup scroll handler to check reset button visibility
* Sticky filter shows when reset button's bottom edge is above (masthead + 10px)
*/ */
setupObserver: function() { setupScrollHandler: function() {
var self = this; var self = this;
this.observer = new IntersectionObserver(function(entries) { $(window).on('scroll.stickyFilters', function() {
entries.forEach(function(entry) { clearTimeout(self.scrollTimeout);
if (entry.isIntersecting) { self.scrollTimeout = setTimeout(function() {
// Main filter is visible - hide sticky (instant) self.checkVisibility();
self.hideStickyFilter(); }, 50);
} else {
// Main filter scrolled out - show sticky (animated)
self.showStickyFilter();
}
});
}, {
root: null,
rootMargin: '0px',
threshold: 0
}); });
},
this.observer.observe(this.$mainFilter[0]); /**
* Check if main filter is obscured and toggle sticky visibility
* Obscured = reset button bottom edge is above (viewport top + masthead height + 10px)
*/
checkVisibility: function() {
var mastheadHeight = this.$masthead.length ? this.$masthead.outerHeight() : 0;
var threshold = mastheadHeight + 10;
var resetButtonRect = this.$resetButton[0].getBoundingClientRect();
// Reset button is obscured when its bottom edge is above the threshold
if (resetButtonRect.bottom < threshold) {
this.showStickyFilter();
} else {
this.hideStickyFilter();
}
}, },
/** /**
@@ -956,6 +980,22 @@
self.syncToSticky(this.name, $(this).val()); self.syncToSticky(this.name, $(this).val());
}); });
// City/Zip mutual exclusivity in sticky form - city clears zip
this.$stickyForm.find('select[name="property_location"]').on('change', function() {
if ($(this).val()) {
self.$stickyForm.find('input[name="zip"]').val('');
self.$mainForm.find('input[name="zip"]').val('');
}
});
// City/Zip mutual exclusivity in sticky form - zip clears city
this.$stickyForm.find('input[name="zip"]').on('input', function() {
if ($(this).val()) {
self.$stickyForm.find('select[name="property_location"]').val('');
self.$mainForm.find('select[name="property_location"]').val('');
}
});
// Sync sticky form -> main form and trigger filter // Sync sticky form -> main form and trigger filter
this.$stickyForm.find('select').on('change', function() { this.$stickyForm.find('select').on('change', function() {
self.syncToMain(this.name, $(this).val()); self.syncToMain(this.name, $(this).val());
@@ -1047,6 +1087,7 @@
mapBounds: null, // Current map viewport bounds mapBounds: null, // Current map viewport bounds
mapCenter: null, // Current map center for distance sorting mapCenter: null, // Current map center for distance sorting
isMapUpdate: false, // Flag to prevent map->filter->map loop isMapUpdate: false, // Flag to prevent map->filter->map loop
isResetTriggered: false, // Flag: true when reset button was clicked
/** /**
* Initialize * Initialize
@@ -1076,6 +1117,34 @@
self.filterProperties(1); self.filterProperties(1);
}); });
// City/Zip mutual exclusivity - city clears zip
this.$form.find('select[name="property_location"]').on('change', function() {
if ($(this).val()) {
self.$form.find('input[name="zip"]').val('');
// Also sync to sticky form if it exists
if (StickyFilters && StickyFilters.$stickyForm) {
StickyFilters.$stickyForm.find('input[name="zip"]').val('');
}
}
});
// City/Zip mutual exclusivity - zip clears city
this.$form.find('input[name="zip"]').on('input', function() {
if ($(this).val()) {
self.$form.find('select[name="property_location"]').val('');
// Also sync to sticky form if it exists
if (StickyFilters && StickyFilters.$stickyForm) {
StickyFilters.$stickyForm.find('select[name="property_location"]').val('');
}
}
});
// Zip code submit on enter or blur
this.$form.find('input[name="zip"]').on('change', function() {
self.clearPinSelection();
self.onFilterChange();
});
// Filter changes (auto-submit on select change) // Filter changes (auto-submit on select change)
this.$form.find('select').on('change', function() { this.$form.find('select').on('change', function() {
self.clearPinSelection(); self.clearPinSelection();
@@ -1480,6 +1549,10 @@
var wasMapUpdate = this.isMapUpdate; var wasMapUpdate = this.isMapUpdate;
this.isMapUpdate = false; this.isMapUpdate = false;
// Capture and clear reset flag
var wasResetTriggered = this.isResetTriggered;
this.isResetTriggered = false;
// Queue the property list request with debounce and cancellation // Queue the property list request with debounce and cancellation
RequestQueue.queue( RequestQueue.queue(
'properties', 'properties',
@@ -1495,6 +1568,11 @@
self.$results.html(response.data.html); self.$results.html(response.data.html);
self.isFirstLoad = false; self.isFirstLoad = false;
// If reset was triggered and no results, reset map to initial position
if (wasResetTriggered && response.data.found_posts === 0 && PropertyMap.map) {
PropertyMap.resetToInitialPosition();
}
// Update map with new filter params (but not if this was triggered by map move) // Update map with new filter params (but not if this was triggered by map move)
if (response.data.filters && !wasMapUpdate) { if (response.data.filters && !wasMapUpdate) {
PropertyMap.updateFilters(response.data.filters); PropertyMap.updateFilters(response.data.filters);
@@ -1681,9 +1759,24 @@
/** /**
* Reset filters * Reset filters
* Clears all form fields and triggers reload.
* If no results after reset, map resets to initial position.
*/ */
resetFilters: function() { resetFilters: function() {
// Clear all selects
this.$form.find('select').val(''); this.$form.find('select').val('');
// Clear zip input
this.$form.find('input[name="zip"]').val('');
// Also sync to sticky form
if (StickyFilters && StickyFilters.$stickyForm) {
StickyFilters.$stickyForm.find('select').val('');
StickyFilters.$stickyForm.find('input[name="zip"]').val('');
}
// Set flag for reset-triggered request
this.isResetTriggered = true;
this.onFilterChange(); this.onFilterChange();
}, },
@@ -473,19 +473,17 @@
letter-spacing: 0.03em; letter-spacing: 0.03em;
} }
.filter-select { .filter-select,
.filter-input {
width: 100%; width: 100%;
padding: 0.625rem 2rem 0.625rem 0.75rem; height: 2.75rem;
background-color: var(--color-bg-dark); padding: 0 0.75rem;
background-color: #000;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: 0.25rem; border-radius: 0.25rem;
color: var(--color-text); color: var(--color-text);
font-size: 0.9375rem; font-size: 0.9375rem;
cursor: pointer; box-sizing: border-box;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23B0B0B0' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
&:focus { &:focus {
outline: none; outline: none;
@@ -493,24 +491,20 @@
} }
} }
.filter-input { .filter-select {
width: 100%; padding-right: 2rem;
padding: 0.625rem 0.75rem; cursor: pointer;
background-color: var(--color-bg-dark); appearance: none;
border: 1px solid var(--color-border); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23B0B0B0' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
border-radius: 0.25rem; background-repeat: no-repeat;
color: var(--color-text); background-position: right 0.75rem center;
font-size: 0.9375rem; }
.filter-input {
&::placeholder { &::placeholder {
color: var(--color-text-muted); color: var(--color-text-muted);
opacity: 0.7; opacity: 0.7;
} }
&:focus {
outline: none;
border-color: var(--color-accent);
}
} }
// Zip code filter - narrower width // Zip code filter - narrower width
@@ -571,12 +565,14 @@
.filter-select, .filter-select,
.filter-input { .filter-input {
width: 100%; width: 100%;
padding: 0.5rem 0.625rem; height: 2.25rem;
padding: 0 0.625rem;
font-size: 0.8125rem; font-size: 0.8125rem;
background-color: var(--color-bg-dark); background-color: #000;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: 0.25rem; border-radius: 0.25rem;
color: var(--color-text); color: var(--color-text);
box-sizing: border-box;
&:focus { &:focus {
outline: none; outline: none;
@@ -602,6 +598,19 @@
} }
} }
.filters-sticky-reset {
display: flex;
justify-content: center;
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--color-border);
.btn {
padding: 0.5rem 1.5rem;
font-size: 0.8125rem;
}
}
// Results loading spinner (only for first load) // Results loading spinner (only for first load)
.property-results-loading { .property-results-loading {
display: flex; display: flex;