@@ -6,7 +6,7 @@ import {Widget} from '@deck.gl/core';
66import type { WidgetPlacement , Viewport , WidgetProps } from '@deck.gl/core' ;
77import { FlyToInterpolator , LinearInterpolator } from '@deck.gl/core' ;
88import { render } from 'preact' ;
9- import { DropdownMenu } from './lib/components/dropdown-menu' ;
9+ import { DropdownMenu , type MenuItem } from './lib/components/dropdown-menu' ;
1010import { type Geocoder } from './lib/geocode/geocoder' ;
1111import { GeocoderHistory } from './lib/geocode/geocoder-history' ;
1212import {
@@ -22,6 +22,15 @@ type ViewState = Record<string, unknown>;
2222
2323const CURRENT_LOCATION = 'current' ;
2424
25+ // Location pin icon (from Google Material Symbols)
26+ const LOCATION_ICON = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M480-480q33 0 56.5-23.5T560-560q0-33-23.5-56.5T480-640q-33 0-56.5 23.5T400-560q0 33 23.5 56.5T480-480Zm0 294q122-112 181-203.5T720-552q0-109-69.5-178.5T480-800q-101 0-170.5 69.5T240-552q0 71 59 162.5T480-186Zm0 106Q319-217 239.5-334.5T160-552q0-150 96.5-239T480-880q127 0 223.5 89T800-552q0 100-79.5 217.5T480-80Z'/%3E%3C/svg%3E` ;
27+
28+ const CURRENT_LOCATION_ITEM : MenuItem = {
29+ label : 'Current location' ,
30+ value : CURRENT_LOCATION ,
31+ icon : LOCATION_ICON
32+ } ;
33+
2534/** Properties for the GeocoderWidget */
2635export type GeocoderWidgetProps = WidgetProps & {
2736 viewId ?: string | null ;
@@ -66,6 +75,7 @@ export class GeocoderWidget extends Widget<GeocoderWidgetProps> {
6675 geocodeHistory = new GeocoderHistory ( { } ) ;
6776 addressText : string = '' ;
6877 geocoder : Geocoder = CoordinatesGeocoder ;
78+ isGettingLocation : boolean = false ;
6979
7080 constructor ( props : GeocoderWidgetProps = { } ) {
7181 super ( props ) ;
@@ -83,45 +93,27 @@ export class GeocoderWidget extends Widget<GeocoderWidgetProps> {
8393 }
8494
8595 onRenderHTML ( rootElement : HTMLElement ) : void {
86- const menuItems = this . props . _geolocation
87- ? [ CURRENT_LOCATION , ...this . geocodeHistory . addressHistory ]
96+ const menuItems : MenuItem [ ] = this . props . _geolocation
97+ ? [ CURRENT_LOCATION_ITEM , ...this . geocodeHistory . addressHistory ]
8898 : [ ...this . geocodeHistory . addressHistory ] ;
8999 render (
90- < div
91- className = "deck-widget-geocoder"
92- style = { {
93- pointerEvents : 'auto' ,
94- display : 'flex' ,
95- alignItems : 'center' ,
96- flexWrap : 'wrap' // Allows wrapping on smaller screens
97- } }
98- >
100+ < div className = "deck-widget-geocoder" >
99101 < input
102+ className = "deck-widget-geocoder-input"
100103 type = "text"
101- placeholder = { this . geocoder . placeholderLocation ?? 'Enter address or location' }
104+ placeholder = {
105+ this . isGettingLocation
106+ ? 'Finding your location...'
107+ : ( this . geocoder . placeholderLocation ?? 'Enter address or location' )
108+ }
102109 value = { this . geocodeHistory . addressText }
103110 // @ts -expect-error event type
104111 onInput = { e => this . setInput ( e . target ?. value || '' ) }
105112 onKeyPress = { this . handleKeyPress }
106- style = { {
107- flex : '1 1 auto' ,
108- minWidth : '200px' ,
109- margin : 0 ,
110- padding : '8px' ,
111- boxSizing : 'border-box'
112- } }
113- />
114- < DropdownMenu
115- menuItems = { menuItems }
116- onSelect = { this . handleSelect }
117- style = { {
118- margin : 2 ,
119- padding : '4px 2px' ,
120- boxSizing : 'border-box'
121- } }
122113 />
114+ < DropdownMenu menuItems = { menuItems } onSelect = { this . handleSelect } />
123115 { this . geocodeHistory . errorText && (
124- < div className = "error" > { this . geocodeHistory . errorText } </ div >
116+ < div className = "deck-widget-geocoder- error" > { this . geocodeHistory . errorText } </ div >
125117 ) }
126118 </ div > ,
127119 rootElement
@@ -138,9 +130,15 @@ export class GeocoderWidget extends Widget<GeocoderWidgetProps> {
138130 }
139131 } ;
140132
141- handleSelect = ( address : string ) => {
142- this . setInput ( address ) ;
143- this . handleSubmit ( ) ;
133+ handleSelect = ( value : string ) => {
134+ if ( value === CURRENT_LOCATION ) {
135+ // Don't put "current" in the text field, just trigger geolocation
136+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
137+ this . getCurrentLocation ( ) ;
138+ } else {
139+ this . setInput ( value ) ;
140+ this . handleSubmit ( ) ;
141+ }
144142 } ;
145143
146144 /** Sync wrapper for async geocode() */
@@ -149,15 +147,39 @@ export class GeocoderWidget extends Widget<GeocoderWidgetProps> {
149147 this . geocode ( this . addressText ) ;
150148 } ;
151149
150+ /** Get current location via browser geolocation API */
151+ getCurrentLocation = async ( ) => {
152+ this . isGettingLocation = true ;
153+ if ( this . rootElement ) {
154+ this . updateHTML ( ) ;
155+ }
156+
157+ try {
158+ const coordinates = await CurrentLocationGeocoder . geocode ( ) ;
159+ if ( coordinates ) {
160+ this . setViewState ( coordinates ) ;
161+ }
162+ } catch ( error ) {
163+ this . geocodeHistory . errorText = error instanceof Error ? error . message : 'Location error' ;
164+ } finally {
165+ this . isGettingLocation = false ;
166+ if ( this . rootElement ) {
167+ this . updateHTML ( ) ;
168+ }
169+ }
170+ } ;
171+
152172 /** Perform geocoding */
153173 geocode : ( address : string ) => Promise < void > = async address => {
154- const useGeolocation = this . props . _geolocation && address === CURRENT_LOCATION ;
155- const geocoder = useGeolocation ? CurrentLocationGeocoder : this . geocoder ;
156174 const coordinates = await this . geocodeHistory . geocode (
157- geocoder ,
175+ this . geocoder ,
158176 this . addressText ,
159177 this . props . apiKey
160178 ) ;
179+ // Re-render to show updated history or error (guard against torn-down widget)
180+ if ( this . rootElement ) {
181+ this . updateHTML ( ) ;
182+ }
161183 if ( coordinates ) {
162184 this . setViewState ( coordinates ) ;
163185 }
0 commit comments