Thanks for the suggestion - I don’t think this solves my issue though. I had originally attempted the datalist method for autosuggest that you linked, but I didn’t like how the browser rendered it so I went for my own markup ¯\_(ツ)_/¯
Side note: this line in the heex is really useful! I had no idea you could add a binary flag with the {elixir code} syntax (I thought it had to be attr={elixir code})
<input type="text" name="q" value={@query} list="matches" placeholder="Search..." {%{readonly: @loading}}/>
I can provide more code though, which probably is helpful 
FullMap hook:
FullMap: {
mounted() {
this.map = new Mapper();
this.map.initMap('full-map');
this.handleEvent('street-data', (payload) => {
geojson = {
'type': 'FeatureCollection',
'features': payload.data.map(street => {
const { geometry, ...properties } = street
return {
type: 'Feature',
geometry: geometry,
properties: properties
}
})
}
this.map.addStreets(geojson, payload.sector_id)
});
this.handleEvent('update-streets', (_payload) => {
this.map.clearStreets()
})
window.addEventListener('zoom-to', (e) => {
this.map.zoom(e.detail)
})
this.map.registerMapEvent('load', () => { this.pushEvent('load-data', { bounds: this.map.mapBounds() }) });
this.map.registerMapEvent('moveend', () => { this.pushEvent('load-data', { bounds: this.map.mapBounds() }) });
}
},
and the Mapper class:
class Mapper {
constructor() {
this.map = null;
}
initMap(container_id) {
mapboxgl.accessToken = document.getElementById(container_id).dataset.apiKey;
this.map = new mapboxgl.Map({
container: container_id,
style: 'mapbox://styles/mapbox/streets-v11',
center: [-122.438434, 37.769397],
zoom: 15,
maxBounds: [
[-122.66336, 37.652987], // Southwest coordinates
[-122.250481, 37.851651] // Northeast coordinates
],
minZoom: 13
});
}
registerMapEvent(event, fun) {
this.map.on(event, fun);
}
mapBounds() {
return this.map.getBounds().toArray();
}
clearStreets() {
if (this.map.getSource('sweep-streets') != undefined) {
this.map.removeLayer('sweep-streets')
this.map.removeSource('sweep-streets')
}
}
zoom(geometry) {
const bounds = new mapboxgl.LngLatBounds(geometry.coordinates[0], geometry.coordinates[0]);
for (const coord of geometry.coordinates) {
bounds.extend(coord);
}
this.map.fitBounds(bounds, { padding: 20 });
}
addStreets(geojson) {
var geojson_source = this.map.getSource('sweep-streets')
if (geojson_source == undefined) {
this.map.addSource('sweep-streets', {
type: 'geojson',
data: geojson
});
this.map.addLayer({
id: 'sweep-streets',
type: 'line',
source: `sweep-streets`,
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': ['get', 'color', ['at', 0, ['get', 'sweeps']]],
'line-width': 3,
}
});
const popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});
this.map.on('mouseenter', 'sweep-streets', (e) => {
this.map.getCanvas().style.cursor = 'pointer';
const coordinates = e.lngLat;
const properties = e.features[0].properties;
const sweeps = JSON.parse(properties.sweeps)
const html = `<div>
<h1 class="font-bold text-lg">${properties.corridor}</h1>
<div class="flex"><p class="flex-1">${properties.limits}</p><p class="mr-2">${properties.block_side}</p></div>
<p>Next street sweeping is <b>${formatDay(sweeps[0].next)}</b> from <b>${formatHour(sweeps[0].from_hour)}</b> to <b>${formatHour(sweeps[0].to_hour)}</b>.</p>
</div>`
popup.setLngLat(coordinates).setHTML(html).addTo(this.map);
});
this.map.on('mouseleave', 'sweep-streets', () => {
this.map.getCanvas().style.cursor = '';
popup.remove();
});
} else {
var new_features = geojson_source._data.features.concat(geojson.features);
geojson_source.setData({ type: "FeatureCollection", features: new_features });
}
}
};