diff --git a/CHANGELOG.md b/CHANGELOG.md index e49bc82..1c804e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,7 @@ ## v0.4.0 - Adds optional callback to options so that developers can take additional action when the user selects a result. + +## v0.5.0 + +- Adds arrow key-based navigation! \ No newline at end of file diff --git a/examples/basic.html b/examples/basic.html index 5cd3e6f..2803aff 100644 --- a/examples/basic.html +++ b/examples/basic.html @@ -21,6 +21,7 @@ var control = new maplibreSearchBox.MapLibreSearchControl({ useMapFocusPoint: true, onResultSelected: feature => { + // You can add code here to take some action when a result is selected. console.log(feature.geometry.coordinates); }, }); diff --git a/package.json b/package.json index ada4bbb..a9ea72f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@stadiamaps/maplibre-search-box", - "version": "0.4.1", + "version": "0.5.0", "homepage": "https://docs.stadiamaps.com/", "repository": "https://github.com/stadiamaps/maplibre-search-box", "license": "BSD-3-Clause", diff --git a/src/index.scss b/src/index.scss index c7219c8..a8d5478 100644 --- a/src/index.scss +++ b/src/index.scss @@ -46,7 +46,8 @@ top: 9px; width: 20px; - &:hover { + &:hover, + &.hover { opacity: 1; } @@ -102,7 +103,8 @@ font-size: 1rem; padding: 5px; - &:hover { + &:hover, + &.hover { background: #dfdfdf; } @@ -114,7 +116,8 @@ .no-result { font-style: italic; - &:hover { + &:hover, + &.hover { background: inherit; cursor: inherit; } diff --git a/src/index.ts b/src/index.ts index f9a7d96..490de99 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,6 +32,8 @@ export class MapLibreSearchControl implements IControl { private lastRequestAt = 0; private lastRequestString = ""; private resultFeatures: PeliasGeoJSONFeature[] = []; + private selectedResultIndex: number | null = null; + private originalInput: string = ""; options = new MapLibreSearchControlOptions(); @@ -67,7 +69,7 @@ export class MapLibreSearchControl implements IControl { this.input.placeholder = "Search for places..."; this.input.addEventListener("input", this.onInput.bind(this)); this.input.addEventListener("focus", this.onFocus.bind(this)); - this.input.addEventListener("keypress", this.onKey.bind(this)); + this.input.addEventListener("keydown", this.onKey.bind(this)); this.input.enterKeyHint = "search"; this.resultsContainer = container.appendChild( @@ -111,6 +113,9 @@ export class MapLibreSearchControl implements IControl { case "Enter": if (this.options.searchOnEnter) { await this.onInput(e, true); + } else if (this.selectedResultIndex !== null) { + this.onSelected(this.resultFeatures[this.selectedResultIndex]); + this.input.blur(); } break; case "Escape": @@ -118,11 +123,44 @@ export class MapLibreSearchControl implements IControl { break; case "ArrowDown": case "ArrowUp": - // TODO + e.preventDefault(); // prevent the input field from consuming the event + this.handleArrowKey(e.key); break; } } + handleArrowKey(key: string) { + if (key === "ArrowDown") { + if (this.selectedResultIndex === null) { + this.selectedResultIndex = 0; + this.originalInput = this.input.value; + } else if (this.selectedResultIndex < this.resultFeatures.length - 1) { + this.selectedResultIndex++; + } + } else if (key === "ArrowUp") { + if (this.selectedResultIndex > 0) { + this.selectedResultIndex--; + } else if (this.selectedResultIndex === 0) { + this.selectedResultIndex = null; + this.input.value = this.originalInput; + } + } + + this.updateSelectedResult(); + } + + updateSelectedResult() { + const results = Array.from(this.resultsList.children); + results.forEach((result, index) => { + if (index === this.selectedResultIndex) { + result.classList.add("hover"); + this.input.value = this.resultFeatures[index].properties.label; + } else { + result.classList.remove("hover"); + } + }); + } + async onInput(e: Event, useSearch = false) { this.maybeShowClearButton(); @@ -263,7 +301,7 @@ export class MapLibreSearchControl implements IControl { default: zoomTarget = 10; } - this.map.jumpTo({ + this.map.flyTo({ center: [ feature.geometry.coordinates[0], feature.geometry.coordinates[1], @@ -291,7 +329,11 @@ export class MapLibreSearchControl implements IControl { buildResult(result: PeliasGeoJSONFeature): HTMLDivElement { const el = document.createElement("div"); el.className = "result"; - el.onclick = this.onSelected.bind(this, result); + el.onclick = () => { + this.selectedResultIndex = this.resultFeatures.indexOf(result); + this.updateSelectedResult(); + this.onSelected(result); + }; el.title = result.properties.label; @@ -334,6 +376,8 @@ export class MapLibreSearchControl implements IControl { clearResults() { this.resultFeatures = []; this.resultsList.replaceChildren(""); + this.selectedResultIndex = null; + this.originalInput = ""; } onRemove(map: Map): void {