Skip to content

Commit

Permalink
Added Autocomplete widget support
Browse files Browse the repository at this point in the history
  • Loading branch information
cgalvan committed Jun 18, 2024
1 parent 55a7f33 commit 75ac099
Show file tree
Hide file tree
Showing 6 changed files with 428 additions and 16 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
},
"dependencies": {
"@aws-sdk/client-location": "^3.535.0",
"@aws/amazon-location-for-maplibre-gl-geocoder": "^1.0.2",
"@aws/amazon-location-for-maplibre-gl-geocoder": "^1.0.3",
"@aws/amazon-location-utilities-auth-helper": "^1.0.6",
"maplibre-gl": "^4.1.1"
},
Expand Down
11 changes: 10 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import {
} from "./googleCommon";
import { MigrationMap } from "./maps";
import { MigrationMarker } from "./markers";
import { MigrationAutocompleteService, MigrationPlacesService, MigrationSearchBox } from "./places";
import {
MigrationAutocomplete,
MigrationAutocompleteService,
MigrationPlacesService,
MigrationSearchBox,
} from "./places";
import { MigrationInfoWindow } from "./infoWindow";

// Dynamically load the MapLibre and MapLibre Geocoder stylesheets so that our migration adapter is the only thing our users need to import
Expand Down Expand Up @@ -77,6 +82,8 @@ const migrationInit = async function () {

// Pass our location client, and optionally place index and route calculator names
// to our migration services
MigrationAutocomplete.prototype._client = client;
MigrationAutocomplete.prototype._placeIndexName = placeIndexName;
MigrationAutocompleteService.prototype._client = client;
MigrationAutocompleteService.prototype._placeIndexName = placeIndexName;
MigrationPlacesService.prototype._client = client;
Expand Down Expand Up @@ -114,6 +121,7 @@ const migrationInit = async function () {
TravelMode: TravelMode,

places: {
Autocomplete: MigrationAutocomplete,
AutocompleteService: MigrationAutocompleteService,
PlacesService: MigrationPlacesService,
PlacesServiceStatus: PlacesServiceStatus,
Expand Down Expand Up @@ -141,6 +149,7 @@ const migrationInit = async function () {

case "places":
resolve({
Autocomplete: MigrationAutocomplete,
AutocompleteService: MigrationAutocompleteService,
PlacesService: MigrationPlacesService,
PlacesServiceStatus: PlacesServiceStatus,
Expand Down
147 changes: 145 additions & 2 deletions src/places.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,144 @@ class MigrationAutocompleteService {
}
}

class MigrationAutocomplete {
_client: LocationClient; // This will be populated by the top level module that creates our location client
_placeIndexName: string; // This will be populated by the top level module that is passed our place index name
#maplibreGeocoder;
#bounds: MigrationLatLngBounds | undefined;
#strictBounds = false;
#fields;
#place;

constructor(inputField: HTMLInputElement, opts?) {
// Same base geocoder options as SearchBox, except we add omitSuggestionsWithoutPlaceId
// so that we only get results with a PlaceId
const maplibreGeocoderOptions: PlacesGeocoderOptions = {
enableAll: true,
omitSuggestionsWithoutPlaceId: true,
};

if (inputField.placeholder) {
maplibreGeocoderOptions.placeholder = inputField.placeholder;
}

this.#maplibreGeocoder = buildAmazonLocationMaplibreGeocoder(
this._client,
this._placeIndexName,
maplibreGeocoderOptions,
);

const geocoder = this.#maplibreGeocoder.getPlacesGeocoder();
geocoder.addTo(inputField.parentElement);

if (inputField.className) {
geocoder.container.className = `${inputField.className} ${geocoder.container.className}`;
}

inputField.remove();

if (opts) {
this.setOptions(opts);
}
}

getBounds() {
return this.#bounds;
}

getFields() {
return this.#fields;
}

getPlace() {
return this.#place;
}

setBounds(bounds) {
this.#bounds = new MigrationLatLngBounds(bounds);

// Google's setBounds is used to bias, but the geocoder's bounds is a firm restriction, so
// if strictBounds isn't specified, then we use the center of the input bounds to bias
if (this.#strictBounds) {
const southWest = this.#bounds.getSouthWest();
const northEast = this.#bounds.getNorthEast();
const boundingBox = {
longitudeSW: southWest.lng(),
latitudeSW: southWest.lat(),
longitudeNE: northEast.lng(),
latitudeNE: northEast.lat(),
};

this.#maplibreGeocoder.setBoundingBox(boundingBox);
} else {
const center = this.#bounds.getCenter();
this.#maplibreGeocoder.setBiasPosition({
latitude: center.lat(),
longitude: center.lng(),
});
}
}

setFields(fields) {
this.#fields = fields;
}

setOptions(options) {
// Read in strictBounds option first since it will determine how
// the bounds option is consumed
if (typeof options?.strictBounds === "boolean") {
this.#strictBounds = options.strictBounds;
}

if (options?.bounds) {
this.setBounds(options.bounds);
}

if (options?.fields) {
this.#fields = options.fields;
}
}

addListener(eventName, handler) {
if (eventName == "place_changed") {
// This event is triggered if the user selects either a place from the retrieved suggestions
this.#maplibreGeocoder.getPlacesGeocoder().on("results", (results) => {
if (results.place) {
// The fields could be set later, so we need to query again before converting the place
const fields = this.#fields || ["ALL"];

this.#place = convertAmazonPlaceToGoogle(results.place.properties, fields, true);

// When the user picks a prediction, the geocoder displays the updated results
// by default (e.g. drops down the single chosen prediction).
// Google's widget does not do this, so in order to force the
// results to collapse, we need to focus and then unfocus the input element.
const inputElement = this.#maplibreGeocoder.getPlacesGeocoder()._inputEl as HTMLInputElement;
inputElement.focus();
inputElement.blur();

handler();
}
});

// This event is triggered if the user re-selects the single place that had been previously selected
// from the list of suggestions
this.#maplibreGeocoder.getPlacesGeocoder().on("result", (result) => {
// The fields could be set later, so we need to query again before converting the place
const fields = this.#fields || ["ALL"];

this.#place = convertAmazonPlaceToGoogle(result.result.properties, fields, true);

handler();
});
}
}

_getMaplibreGeocoder() {
return this.#maplibreGeocoder;
}
}

class MigrationSearchBox {
_client: LocationClient; // This will be populated by the top level module that creates our location client
_placeIndexName: string; // This will be populated by the top level module that is passed our place index name
Expand All @@ -368,7 +506,12 @@ class MigrationSearchBox {
this.setBounds(opts.bounds);
}

this.#maplibreGeocoder.getPlacesGeocoder().addTo(inputField.parentElement);
const geocoder = this.#maplibreGeocoder.getPlacesGeocoder();
geocoder.addTo(inputField.parentElement);

if (inputField.className) {
geocoder.container.className = `${inputField.className} ${geocoder.container.className}`;
}

inputField.remove();
}
Expand Down Expand Up @@ -433,4 +576,4 @@ class MigrationSearchBox {
}
}

export { MigrationAutocompleteService, MigrationPlacesService, MigrationSearchBox };
export { MigrationAutocomplete, MigrationAutocompleteService, MigrationPlacesService, MigrationSearchBox };
7 changes: 4 additions & 3 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ test("importing the adapter should populate google.maps namespace for direct loa
expect(google.maps).toHaveProperty("TravelMode");

// Places classes
expect(google.maps.places).toHaveProperty("Autocomplete");
expect(google.maps.places).toHaveProperty("AutocompleteService");
expect(google.maps.places).toHaveProperty("PlacesService");
expect(google.maps.places).toHaveProperty("PlacesServiceStatus");
Expand Down Expand Up @@ -85,10 +86,10 @@ test("can dynamically import places classes", async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const google = (window as any).google;

const { AutocompleteService, PlacesService, PlacesServiceStatus, SearchBox } = await google.maps.importLibrary(
"places",
);
const { Autocomplete, AutocompleteService, PlacesService, PlacesServiceStatus, SearchBox } =
await google.maps.importLibrary("places");

expect(Autocomplete).toBeDefined();
expect(AutocompleteService).toBeDefined();
expect(PlacesService).toBeDefined();
expect(PlacesServiceStatus).toBeDefined();
Expand Down
Loading

0 comments on commit 75ac099

Please sign in to comment.