Fix camera jumping when opening InfoPanel
All checks were successful
Build Docker Image / build (push) Successful in 4m17s
All checks were successful
Build Docker Image / build (push) Successful in 4m17s
Replace setTimeout/debounce with event-based approach: - Add isInfoLoading computed that tracks all info loading states - Pass infoLoading prop through CatalogPage to CatalogMap - Watch infoLoading transition from true->false to trigger fitBounds - Remove setTimeout hack in favor of proper loading state detection
This commit is contained in:
@@ -51,6 +51,7 @@ const props = withDefaults(defineProps<{
|
||||
entityType?: 'offer' | 'hub' | 'supplier'
|
||||
initialCenter?: [number, number]
|
||||
initialZoom?: number
|
||||
infoLoading?: boolean
|
||||
relatedPoints?: Array<{
|
||||
uuid: string
|
||||
name: string
|
||||
@@ -64,6 +65,7 @@ const props = withDefaults(defineProps<{
|
||||
initialCenter: () => [37.64, 55.76],
|
||||
initialZoom: 2,
|
||||
useServerClustering: false,
|
||||
infoLoading: false,
|
||||
items: () => [],
|
||||
clusteredPoints: () => [],
|
||||
relatedPoints: () => []
|
||||
@@ -693,11 +695,8 @@ watch(() => props.hoveredItem, () => {
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// Debounced fitBounds to avoid camera jumping when points load incrementally
|
||||
let fitBoundsTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
// Update related points layer when relatedPoints changes + fit bounds (debounced)
|
||||
watch(() => props.relatedPoints, (points) => {
|
||||
// Update related points layer when relatedPoints changes
|
||||
watch(() => props.relatedPoints, () => {
|
||||
if (!mapRef.value || !mapInitialized.value) return
|
||||
|
||||
// Update the source data immediately
|
||||
@@ -705,26 +704,23 @@ watch(() => props.relatedPoints, (points) => {
|
||||
if (source) {
|
||||
source.setData(relatedPointsGeoJson.value)
|
||||
}
|
||||
|
||||
// Debounce fitBounds - wait for all points to load before zooming
|
||||
if (fitBoundsTimeout) {
|
||||
clearTimeout(fitBoundsTimeout)
|
||||
}
|
||||
|
||||
if (points && points.length > 0) {
|
||||
fitBoundsTimeout = setTimeout(() => {
|
||||
if (!mapRef.value) return
|
||||
const bounds = new LngLatBounds()
|
||||
points.forEach(p => {
|
||||
bounds.extend([p.longitude, p.latitude])
|
||||
})
|
||||
if (!bounds.isEmpty()) {
|
||||
mapRef.value.fitBounds(bounds, { padding: 80, maxZoom: 12 })
|
||||
}
|
||||
}, 300) // Wait 300ms for all points to load
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// Fit bounds when info loading finishes (all related data loaded)
|
||||
watch(() => props.infoLoading, (loading, wasLoading) => {
|
||||
// Only fit bounds when loading changes from true to false (data finished loading)
|
||||
if (wasLoading && !loading && props.relatedPoints && props.relatedPoints.length > 0) {
|
||||
if (!mapRef.value) return
|
||||
const bounds = new LngLatBounds()
|
||||
props.relatedPoints.forEach(p => {
|
||||
bounds.extend([p.longitude, p.latitude])
|
||||
})
|
||||
if (!bounds.isEmpty()) {
|
||||
mapRef.value.fitBounds(bounds, { padding: 80, maxZoom: 12 })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Watch for pointColor or entityType changes - update colors and icons
|
||||
watch([() => props.pointColor, () => props.entityType], async ([newColor, newType]) => {
|
||||
if (!mapRef.value || !mapInitialized.value) return
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
:hovered-item-id="hoveredId"
|
||||
:hovered-item="hoveredItem"
|
||||
:related-points="relatedPoints"
|
||||
:info-loading="infoLoading"
|
||||
@select-item="onMapSelect"
|
||||
@bounds-change="onBoundsChange"
|
||||
/>
|
||||
@@ -250,6 +251,7 @@ const props = withDefaults(defineProps<{
|
||||
items?: MapItem[]
|
||||
showPanel?: boolean
|
||||
filterByBounds?: boolean
|
||||
infoLoading?: boolean
|
||||
relatedPoints?: Array<{
|
||||
uuid: string
|
||||
name: string
|
||||
@@ -266,6 +268,7 @@ const props = withDefaults(defineProps<{
|
||||
items: () => [],
|
||||
showPanel: false,
|
||||
filterByBounds: false,
|
||||
infoLoading: false,
|
||||
relatedPoints: () => []
|
||||
})
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
:show-panel="showPanel"
|
||||
:filter-by-bounds="filterByBounds"
|
||||
:related-points="relatedPoints"
|
||||
:info-loading="isInfoLoading"
|
||||
@select="onMapSelect"
|
||||
@bounds-change="onBoundsChange"
|
||||
@update:filter-by-bounds="$event ? setBoundsInUrl(currentMapBounds) : clearBoundsFromUrl()"
|
||||
@@ -356,6 +357,11 @@ watch(searchTrigger, () => {
|
||||
// Loading state
|
||||
const isLoading = computed(() => offersLoading.value || selectionLoading.value)
|
||||
|
||||
// Info loading state for map fitBounds (true while any info data is still loading)
|
||||
const isInfoLoading = computed(() =>
|
||||
infoLoading.value || isLoadingProducts.value || isLoadingHubs.value || isLoadingSuppliers.value || isLoadingOffers.value
|
||||
)
|
||||
|
||||
// Show panel when selecting OR when showing info OR when showing quote results
|
||||
const showPanel = computed(() => {
|
||||
return selectMode.value !== null || infoId.value !== null || showQuoteResults.value
|
||||
|
||||
Reference in New Issue
Block a user