├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── filament-google-autocomplete-field.php ├── resources └── lang │ ├── en │ └── filament-google-autocomplete-field.php │ └── it │ └── filament-google-autocomplete-field.php └── src ├── Concerns ├── CanFormatGoogleParams.php └── HasGooglePlaceApi.php ├── FilamentGoogleAutocompleteServiceProvider.php └── Forms └── Components └── GoogleAutocomplete.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `filament-google-autocomplete-field` will be documented in this file. 4 | 5 | ## v1.0.13 - 2025-05-16 6 | 7 | ### What's Changed 8 | 9 | * Bump dependabot/fetch-metadata from 2.3.0 to 2.4.0 by @dependabot in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/34 10 | * Allow the use of different form layouts by @andreia in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/35 11 | 12 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.12...v1.0.13 13 | 14 | ## v1.0.12 - 2025-04-04 15 | 16 | ### What's Changed 17 | 18 | * Reset autocomplete field when performing a new search by @AlexandreCConcept in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/32 19 | 20 | ### New Contributors 21 | 22 | * @AlexandreCConcept made their first contribution in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/32 23 | 24 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.11...v1.0.12 25 | 26 | ## v1.0.11 - 2025-03-15 27 | 28 | ### What's Changed 29 | 30 | * Add placeholder by @andreia in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/25 31 | 32 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.10...v1.0.11 33 | 34 | ## v1.0.10 - 2025-03-10 35 | 36 | ### What's Changed 37 | 38 | * Add Laravel 12 compatibility by @andreia in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/26 39 | 40 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.9...v1.0.10 41 | 42 | ## v1.0.9 - 2025-02-17 43 | 44 | ### What's Changed 45 | 46 | * Add language in Place Details by @andreia in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/19 47 | 48 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.8...v1.0.9 49 | 50 | ## v1.0.8 - 2025-02-10 51 | 52 | ### What's Changed 53 | 54 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/13 55 | * Bump aglipanci/laravel-pint-action from 2.4 to 2.5 by @dependabot in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/15 56 | * Method for customising the label of the Google autocomplete select field by @alexb34n in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/17 57 | 58 | ### New Contributors 59 | 60 | * @alexb34n made their first contribution in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/17 61 | 62 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.7...v1.0.8 63 | 64 | ## v1.0.7 - 2025-01-13 65 | 66 | ### What's Changed 67 | 68 | * Allow multiple autocomplete instances by @andreia in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/12 69 | 70 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.6...v1.0.7 71 | 72 | ## v1.0.6 - 2025-01-05 73 | 74 | ### What's Changed 75 | 76 | * Add support to Google Places API (New) by @andreia in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/11 77 | 78 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.5...v1.0.6 79 | 80 | ## v1.0.5 - 2024-12-30 81 | 82 | ### What's Changed 83 | 84 | * Call afterStateUpdated hook on value change by @JacobDelcroix in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/10 85 | 86 | ### New Contributors 87 | 88 | * @JacobDelcroix made their first contribution in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/10 89 | 90 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.4...v1.0.5 91 | 92 | ## v1.0.4 - 2024-12-02 93 | 94 | ### What's Changed 95 | 96 | * Added italian language support by @matteo3105wbl in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/7 97 | 98 | ### New Contributors 99 | 100 | * @matteo3105wbl made their first contribution in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/7 101 | 102 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.3...v1.0.4 103 | 104 | ## v1.0.3 - 2024-10-27 105 | 106 | ### What's Changed 107 | 108 | * Fixes error thrown if administrative_area_level_2 is missing in search result by @bcash in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/6 109 | 110 | ### New Contributors 111 | 112 | * @bcash made their first contribution in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/6 113 | 114 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.2...v1.0.3 115 | 116 | ## v1.0.2 - 2024-08-05 117 | 118 | ### What's Changed 119 | 120 | * Prevent google_autocomplete select from being dehydrated by @andreia in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/5 121 | 122 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.1...v1.0.2 123 | 124 | ## v1.0.1 - 2024-07-25 125 | 126 | ### What's Changed 127 | 128 | * Fix empty state on google autocomplete select by @andreia in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/3 129 | 130 | ### New Contributors 131 | 132 | * @andreia made their first contribution in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/3 133 | 134 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/compare/v1.0.0...v1.0.1 135 | 136 | ## v1.0.0 - 2024-07-14 137 | 138 | ### What's Changed 139 | 140 | * Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/1 141 | 142 | ### New Contributors 143 | 144 | * @dependabot made their first contribution in https://github.com/TappNetwork/filament-google-autocomplete-field/pull/1 145 | 146 | **Full Changelog**: https://github.com/TappNetwork/filament-google-autocomplete-field/commits/v1.0.0 147 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Tapp Network 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Filament Google Autcomplete Field 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/tapp/filament-google-autocomplete-field.svg?style=flat-square)](https://packagist.org/packages/tapp/filament-google-autocomplete-field) 4 | ![GitHub Tests Action Status](https://github.com/TappNetwork/filament-google-autocomplete-field/actions/workflows/run-tests.yml/badge.svg) 5 | ![GitHub Code Style Action Status](https://github.com/TappNetwork/filament-google-autocomplete-field/actions/workflows/fix-php-code-style-issues.yml/badge.svg) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/tapp/filament-google-autocomplete-field.svg?style=flat-square)](https://packagist.org/packages/tapp/filament-google-autocomplete-field) 7 | 8 | This plugin provides an address autocomplete using [Google Place autocomplete API](https://developers.google.com/maps/documentation/places/web-service/autocomplete), with fully customizable address fields. 9 | 10 | > [!NOTE] 11 | > The [Google Places API package](https://github.com/SachinAgarwal1337/google-places-api) is used to make API requests to Google Places. 12 | 13 | ## Installation 14 | 15 | You can install the package via composer: 16 | 17 | ```bash 18 | composer require tapp/filament-google-autocomplete-field 19 | ``` 20 | 21 | You can publish the config file with: 22 | 23 | ```bash 24 | php artisan vendor:publish --tag="filament-google-autocomplete-field-config" 25 | ``` 26 | 27 | This is the contents of the published config file: 28 | 29 | ```php 30 | return [ 31 | 32 | 'api-key' => env('GOOGLE_PLACES_API_KEY', ''), 33 | 'verify-ssl' => true, 34 | 'throw-on-errors' => false, 35 | 36 | ]; 37 | ``` 38 | 39 | Optionally, you can publish the translation files with: 40 | 41 | ```bash 42 | php artisan vendor:publish --tag="filament-google-autocomplete-field-translations" 43 | ``` 44 | 45 | ## Setup 46 | 47 | On [Google console](https://console.cloud.google.com/apis/dashboard), create an application and enable the Places API. 48 | 49 | 1. Click on "ENABLE APIS AND SERVICES" 50 | 2. Search for "Places api" 51 | 3. Click to enable it 52 | 4. Get the API key 53 | 54 | In your Laravel application, add the Google API key to `GOOGLE_PLACES_API_KEY` in your `.env` file: 55 | 56 | ```php 57 | GOOGLE_PLACES_API_KEY=your_google_place_api_key_here 58 | ``` 59 | 60 | ## Appareance 61 | 62 | ![Filament Google Autcomplete Field](https://raw.githubusercontent.com/TappNetwork/filament-google-autocomplete-field/main/docs/autocomplete02.png) 63 | 64 | ![Filament Google Autcomplete Field](https://raw.githubusercontent.com/TappNetwork/filament-google-autocomplete-field/main/docs/autocomplete03.png) 65 | 66 | ![Filament Google Autcomplete Field](https://raw.githubusercontent.com/TappNetwork/filament-google-autocomplete-field/main/docs/autocomplete04.png) 67 | 68 | ![Filament Google Autcomplete Field](https://raw.githubusercontent.com/TappNetwork/filament-google-autocomplete-field/main/docs/autocomplete05.png) 69 | 70 | ![Filament Google Autcomplete Field](https://raw.githubusercontent.com/TappNetwork/filament-google-autocomplete-field/main/docs/autocomplete06.png) 71 | 72 | ## Usage 73 | 74 | Add the `GoogleAutocomplete` field to your form: 75 | 76 | ```php 77 | use Tapp\FilamentGoogleAutocomplete\Forms\Components\GoogleAutocomplete; 78 | 79 | GoogleAutocomplete::make('google_search'), 80 | ``` 81 | 82 | This will use the default address fields and Google API options. You can also customize the address fields and Google Place API options. See all the options available on [`Available options`](#available-options) item below. For example: 83 | 84 | ```php 85 | use Tapp\FilamentGoogleAutocomplete\Forms\Components\GoogleAutocomplete; 86 | 87 | GoogleAutocomplete::make('google_search') 88 | ->label('Google look-up') 89 | ->countries([ 90 | 'US', 91 | 'AU', 92 | ]) 93 | ->language('pt-BR') 94 | ->withFields([ 95 | Forms\Components\TextInput::make('address') 96 | ->extraInputAttributes([ 97 | 'data-google-field' => '{street_number} {route}, {sublocality_level_1}', 98 | ]), 99 | Forms\Components\TextInput::make('country'), 100 | Forms\Components\TextInput::make('coordinates') 101 | ->extraInputAttributes([ 102 | 'data-google-field' => '{latitude}, {longitude}', 103 | ]), 104 | ]), 105 | ``` 106 | 107 | ### Using form layouts 108 | 109 | The Google autocomplete fields can be wrapped in a Form layout like `Fieldset` or `Section`: 110 | 111 | ```php 112 | Forms\Components\Fieldset::make('Google Search') 113 | ->schema([ 114 | GoogleAutocomplete::make('google_search_field') 115 | // ... 116 | ]), 117 | ``` 118 | 119 | ![Fieldset Layout](https://raw.githubusercontent.com/TappNetwork/filament-google-autocomplete-field/main/docs/fieldset_layout.png) 120 | 121 | ```php 122 | Forms\Components\Section::make('Google Search') 123 | ->schema([ 124 | GoogleAutocomplete::make('google_search_field') 125 | // ... 126 | ]), 127 | ``` 128 | 129 | ![Section Layout](https://raw.githubusercontent.com/TappNetwork/filament-google-autocomplete-field/main/docs/section_layout.png) 130 | 131 | ## Places API (original) and Places API (New) 132 | 133 | Both the **[Places API (original)](https://developers.google.com/maps/documentation/places/web-service/autocomplete)** and the **[Places API (New)](https://developers.google.com/maps/documentation/places/web-service/place-autocomplete)** are supported. 134 | By default, the Places API (original) it's used. To use the Places API (New) instead, add the `->placesApiNew()` method, like so: 135 | 136 | ```php 137 | GoogleAutocomplete::make('google_search') 138 | ->placesApiNew() 139 | ``` 140 | 141 | ## Available Options 142 | 143 | ### Autocompleted Fields 144 | 145 | You can use the `withFields` method to define the fields that will be autocompleted. 146 | 147 | By default the following fields are set if this method isn't provided: 148 | 149 | ```php 150 | Forms\Components\TextInput::make('address') 151 | ->extraInputAttributes([ 152 | 'data-google-field' => '{street_number} {route}, {sublocality_level_1}', 153 | ]), 154 | Forms\Components\TextInput::make('city') 155 | ->extraInputAttributes([ 156 | 'data-google-field' => 'locality', 157 | ]), 158 | Forms\Components\TextInput::make('country'), 159 | Forms\Components\TextInput::make('zip') 160 | ->extraInputAttributes([ 161 | 'data-google-field' => 'postal_code', 162 | ]), 163 | ``` 164 | 165 | You can override these default fields by passing an array of Filament form fields to `withFields` method: 166 | 167 | ```php 168 | GoogleAutocompleteFields::make('google_search') 169 | ->withFields([ 170 | Forms\Components\TextInput::make('address') 171 | ->extraInputAttributes([ 172 | 'data-google-field' => '{street_number} {route}, {sublocality_level_1}', 173 | ]), 174 | Forms\Components\TextInput::make('city') 175 | ->extraInputAttributes([ 176 | 'data-google-field' => 'locality', 177 | ]), 178 | ]), 179 | ``` 180 | 181 | #### Combining Fields 182 | 183 | You can combine multiple fields returned by Google API in one field using curly braces `{}` to wrap the fields in `'data-google-field'` extra input attribute. For example, the `address` field below will contain the `street_number`, `route`, and `sublocality_level_1` and the `coordinates` field will contain the `latitude` and `longitude` fields: 184 | 185 | ```php 186 | Forms\Components\TextInput::make('address') 187 | ->extraInputAttributes([ 188 | 'data-google-field' => '{street_number} {route}, {sublocality_level_1}', 189 | ]), 190 | Forms\Components\TextInput::make('coordinates') 191 | ->extraInputAttributes([ 192 | 'data-google-field' => '{latitude},{longitude}', 193 | ]), 194 | ``` 195 | 196 | #### Field Name 197 | 198 | If your database field have a different name than the Google field (for example you DB field is `zip` and you want to use the Google's `postal_code` value returned by API), you can tie the API field to the DB field by passing your DB field name to `'data-google-field'` on `extraInputAttributes` method like so: 199 | 200 | ```php 201 | Forms\Components\TextInput::make('zip') 202 | ->extraInputAttributes([ 203 | 'data-google-field' => 'postal_code', 204 | ]) 205 | ``` 206 | 207 | These are the names of the Google metadata fields available to use: 208 | 209 | ``` 210 | street_number, 211 | route, 212 | locality, 213 | sublocality_level_1, 214 | administrative_area_level_2, 215 | administrative_area_level_1, 216 | country, 217 | postal_code, 218 | place_id, 219 | formatted_address, 220 | formatted_phone_number, 221 | international_phone_number, 222 | name, 223 | website, 224 | latitude, 225 | longitude, 226 | ``` 227 | 228 | #### long_name and short_name 229 | 230 | Google's Places API returns `long_name` and `short_name` values for address fields. You can choose which one to display by passing it to the `'data-google-value'` on `extraInputAttributes` method: 231 | 232 | ```php 233 | Forms\Components\TextInput::make('country') 234 | ->extraInputAttributes([ 235 | 'data-google-value' => 'short_name', 236 | ]) 237 | ``` 238 | 239 | E.g. of `long_name` and `short_name` data returned by Google's API: 240 | 241 | ```php 242 | "street_number" => [ 243 | "long_name" => "1535" 244 | "short_name" => "1535" 245 | ] 246 | "route" => [ 247 | "long_name" => "Broadway" 248 | "short_name" => "Broadway" 249 | ] 250 | "locality" => [ 251 | "long_name" => "New York" 252 | "short_name" => "New York" 253 | ] 254 | "sublocality_level_1" => [ 255 | "long_name" => "Manhattan" 256 | "short_name" => "Manhattan" 257 | ] 258 | "administrative_area_level_2" => [ 259 | "long_name" => "New York County" 260 | "short_name" => "New York County" 261 | ] 262 | "administrative_area_level_1" => [ 263 | "long_name" => "New York" 264 | "short_name" => "NY" 265 | ] 266 | "country" => [ 267 | "long_name" => "United States" 268 | "short_name" => "US" 269 | ] 270 | "postal_code" => [ 271 | "long_name" => "10036" 272 | "short_name" => "10036" 273 | ] 274 | ``` 275 | 276 | ### Autocomplete Field Column Span 277 | 278 | The default column span for autcomplete select field is `'full'`. You can define other value (same as supported by Filament's `columnSpan()`) using the `autocompleteFieldColumnSpan` method: 279 | 280 | ```php 281 | GoogleAutocomplete::make('google_search') 282 | ->autocompleteFieldColumnSpan(1) 283 | ``` 284 | 285 | ### Autocomplete Field Search Debounce 286 | 287 | The default search debounce is 2 seconds to avoid too many requests to Google Places API. You can define other value using `autocompleteSearchDebounce` method: 288 | 289 | ```php 290 | GoogleAutocomplete::make('google_search') 291 | ->autocompleteSearchDebounce(1000) // 1 second 292 | ``` 293 | 294 | ### Autocomplete Label 295 | 296 | The label of the autocomplete select field can be modified using the `->autocompleteLabel()` method: 297 | 298 | ```php 299 | GoogleAutocomplete::make('google_search') 300 | ->autocompleteLabel('Select a location') 301 | ``` 302 | 303 | ### Autocomplete Placeholder 304 | 305 | The placeholder can be modified using the `->autocompletePlaceholder()` method: 306 | 307 | ```php 308 | GoogleAutocomplete::make('google_search') 309 | ->autocompletePlaceholder('Select a location') 310 | ``` 311 | 312 | Example with modified `label`, `autocompleteLabel`, and `autocompletePlaceholder`: 313 | 314 | ```php 315 | GoogleAutocomplete::make('google_search') 316 | ->autocompleteLabel('Select a location') 317 | ->autocompletePlaceholder('Click here to search') 318 | ->label('Searching on Google...') 319 | ->countries([ 320 | 'us', 321 | 'au', 322 | ]) 323 | ->placeTypes([ 324 | 'book_store', 325 | 'cafe', 326 | ]) 327 | ->withFields([ 328 | Forms\Components\TextInput::make('address') 329 | ->extraInputAttributes([ 330 | 'data-google-field' => '{street_number} {route}, {sublocality_level_1}', 331 | ]), 332 | Forms\Components\TextInput::make('city') 333 | ->extraInputAttributes([ 334 | 'data-google-field' => 'locality', 335 | ]), 336 | ]), 337 | ``` 338 | 339 | ![Example with modified label, autocompleteLabel, and autocompletePlaceholder](https://raw.githubusercontent.com/TappNetwork/filament-google-autocomplete-field/main/docs/label_placeholder.jpg) 340 | 341 | ## Google API Options 342 | 343 | These following **Google API options** can be passed to the `GoogleAutocomplete` field: 344 | 345 | ### OPTIONS FOR BOTH APIs 346 | 347 | ### PlaceTypes 348 | 349 | Restrict the results to be of a certain type. Pass the [available types](https://developers.google.com/maps/documentation/places/web-service/supported_types) as an array: 350 | 351 | ```php 352 | GoogleAutocomplete::make('google_search') 353 | ->placeTypes([ 354 | 'lodging', 355 | 'book_store', 356 | 'florist', 357 | ]) 358 | ``` 359 | 360 | Please refer to the [Google Places API original documentation](https://developers.google.com/maps/documentation/places/web-service/autocomplete#types) and [Google Places API New documentation](https://developers.google.com/maps/documentation/places/web-service/place-autocomplete#includedPrimaryTypes) to a detailed description of this option. 361 | 362 | ### Language 363 | 364 | The language which results should be returned. These are the [supported language codes](https://developers.google.com/maps/faq#languagesupport). 365 | 366 | ```php 367 | GoogleAutocomplete::make('google_search') 368 | ->language('pt-BR') 369 | ``` 370 | 371 | ### Offset 372 | 373 | The position, in the input term, of the last character that the service uses to match predictions. For example, if the input is Google and the offset is 3, the service will match on Goo. 374 | 375 | ```php 376 | GoogleAutocomplete::make('google_search') 377 | ->offset(5) 378 | ``` 379 | 380 | ### LocationBias 381 | 382 | Prefer results in a specified area, by specifying either a radius plus lat/lng, or two lat/lng pairs representing the points of a rectangle. If this parameter is not specified, the API uses IP address biasing by default. 383 | 384 | Please refer to the [Google Places API original documentation](https://developers.google.com/maps/documentation/places/web-service/autocomplete#locationbias) and [Google Places API New](https://developers.google.com/maps/documentation/places/web-service/place-autocomplete#location-bias-restriction) to a detailed description of this option. 385 | 386 | ```php 387 | GoogleAutocomplete::make('google_search') 388 | ->locationBias( 389 | [ 390 | "circle" => [ 391 | "center" => [ 392 | "latitude" => 37.7937, 393 | "longitude" => -122.3965 394 | ], 395 | "radius" => 500.0 396 | ] 397 | ] 398 | ) 399 | ``` 400 | 401 | ### LocationRestriction 402 | 403 | Restrict results to a specified area, by specifying either a radius plus lat/lng, or two lat/lng pairs representing the points of a rectangle. 404 | 405 | Please refer to the [Google Places API original documentation](https://developers.google.com/maps/documentation/places/web-service/autocomplete#locationrestriction) and [Google Places API New](https://developers.google.com/maps/documentation/places/web-service/place-autocomplete#location-bias-restriction) to a detailed description of this option. 406 | 407 | ### Origin 408 | 409 | The origin point as `latitude,longitude` from which to calculate straight-line distance to the destination specified. 410 | 411 | Please refer to the [Google documentation](https://developers.google.com/maps/documentation/places/web-service/autocomplete#origin) to a detailed description of this option. 412 | 413 | ```php 414 | GoogleAutocomplete::make('google_search') 415 | ->origin(40.7585862,-73.9858202) 416 | ``` 417 | 418 | ### Region 419 | 420 | The region code used to format the response, specified as a [country code top-level domain (ccTLD)](https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains#Country_code_top-level_domains) two-character value. 421 | 422 | ```php 423 | GoogleAutocomplete::make('google_search') 424 | ->region('uk') 425 | ``` 426 | 427 | ### SessionToken 428 | 429 | Random string which identifies an autocomplete session for billing purposes. 430 | 431 | Please refer to the [Google documentation](https://developers.google.com/maps/documentation/places/web-service/autocomplete#sessiontoken) to a detailed description of this option. 432 | 433 | 434 | ### OPTIONS ONLY FOR PLACES API (ORIGINAL) 435 | 436 | ### Countries 437 | 438 | Add the `countries` method to restrict the countries that should be used for autocomplete search. 439 | 440 | The countries must be passed as a two character ISO 3166-1 Alpha-2 compatible country code. You can find the country codes available at [Wikipedia: List of ISO 3166 country codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). 441 | 442 | ```php 443 | GoogleAutocomplete::make('google_search') 444 | ->countries([ 445 | 'US', 446 | 'AU', 447 | ]) 448 | ``` 449 | 450 | ### Location 451 | 452 | The point around which to retrieve place information as `latitude,longitude`. 453 | 454 | Please refer to the [Google documentation](https://developers.google.com/maps/documentation/places/web-service/autocomplete#location) to a detailed description of this option. 455 | 456 | ```php 457 | GoogleAutocomplete::make('google_search') 458 | ->location(40.7585862,-73.9858202) 459 | ``` 460 | 461 | ### Radius 462 | 463 | The distance in meters within which to return place results. 464 | 465 | Please refer to the [Google documentation](https://developers.google.com/maps/documentation/places/web-service/autocomplete#radius) to a detailed description of this option. 466 | 467 | ```php 468 | GoogleAutocomplete::make('google_search') 469 | ->radius(10) 470 | ``` 471 | 472 | 473 | ### OPTIONS ONLY FOR PLACES API (NEW) 474 | 475 | ### IncludePureServiceAreaBusinesses 476 | 477 | `true` - includes businesses that visit or deliver to customers directly, but don't have a physical business location. 478 | `false` - returns only businesses with a physical business location. 479 | 480 | ```php 481 | GoogleAutocomplete::make('google_search') 482 | ->includePureServiceAreaBusinesses(true) 483 | ``` 484 | 485 | ### IncludedRegionCodes 486 | 487 | Only include results from the list of specified regions, specified as an array of up to 15 ccTLD ("top-level domain") two-character values. When omitted, no restrictions are applied to the response. 488 | 489 | ```php 490 | GoogleAutocomplete::make('google_search') 491 | ->includedRegionCodes([ 492 | "de", 493 | "fr", 494 | ]) 495 | ``` 496 | 497 | 498 | ## Testing 499 | 500 | ```bash 501 | composer test 502 | ``` 503 | 504 | ## Changelog 505 | 506 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 507 | 508 | ## Contributing 509 | 510 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 511 | 512 | ## Security Vulnerabilities 513 | 514 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 515 | 516 | ## Credits 517 | 518 | - [Tapp Network](https://github.com/TappNetwork) 519 | - [All Contributors](../../contributors) 520 | 521 | ## License 522 | 523 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 524 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tapp/filament-google-autocomplete-field", 3 | "description": "Filament plugin that provides a Google Autocomplete field", 4 | "keywords": [ 5 | "Tapp Network", 6 | "laravel", 7 | "Address", 8 | "Google Autocomplete", 9 | "Google Places API", 10 | "Filament", 11 | "PHP", 12 | "filament-google-autocomplete-field" 13 | ], 14 | "homepage": "https://github.com/tapp/filament-google-autocomplete-field", 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "Andreia Bohner", 19 | "email": "andreia.bohner@tappnetwork.com", 20 | "role": "Developer" 21 | } 22 | ], 23 | "require": { 24 | "php": "^8.2", 25 | "spatie/laravel-package-tools": "^1.16", 26 | "filament/filament": "^3.0-stable", 27 | "illuminate/contracts": "^10.0||^11.0||^12.0", 28 | "skagarwal/google-places-api": "^3.0" 29 | }, 30 | "require-dev": { 31 | "larastan/larastan": "^2.9|^3.0", 32 | "laravel/pint": "^1.14", 33 | "nunomaduro/collision": "^8.1.1||^7.10.0", 34 | "orchestra/testbench": "^8.22.0||^9.0.0||^10.0.0", 35 | "pestphp/pest": "^2.34||^3.0", 36 | "pestphp/pest-plugin-arch": "^2.7||^3.0", 37 | "pestphp/pest-plugin-laravel": "^2.3||^3.0", 38 | "pestphp/pest-plugin-livewire": "^2.1||^3.0", 39 | "phpstan/extension-installer": "^1.3", 40 | "phpstan/phpstan-deprecation-rules": "^1.1||^2.0", 41 | "phpstan/phpstan-phpunit": "^1.3||^2.0", 42 | "spatie/laravel-ray": "^1.35" 43 | }, 44 | "autoload": { 45 | "psr-4": { 46 | "Tapp\\FilamentGoogleAutocomplete\\": "src/" 47 | } 48 | }, 49 | "autoload-dev": { 50 | "psr-4": { 51 | "Tapp\\FilamentGoogleAutocomplete\\Tests\\": "tests/", 52 | "Workbench\\App\\": "workbench/app/" 53 | } 54 | }, 55 | "scripts": { 56 | "post-autoload-dump": "@composer run prepare", 57 | "clear": "@php vendor/bin/testbench package:purge-filament-google-autocomplete-field --ansi", 58 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 59 | "build": [ 60 | "@composer run prepare", 61 | "@php vendor/bin/testbench workbench:build --ansi" 62 | ], 63 | "start": [ 64 | "Composer\\Config::disableProcessTimeout", 65 | "@composer run build", 66 | "@php vendor/bin/testbench serve" 67 | ], 68 | "analyse": "vendor/bin/phpstan analyse", 69 | "test": "vendor/bin/pest", 70 | "test-coverage": "vendor/bin/pest --coverage", 71 | "format": "vendor/bin/pint" 72 | }, 73 | "config": { 74 | "sort-packages": true, 75 | "allow-plugins": { 76 | "pestphp/pest-plugin": true, 77 | "phpstan/extension-installer": true 78 | } 79 | }, 80 | "extra": { 81 | "laravel": { 82 | "providers": [ 83 | "Tapp\\FilamentGoogleAutocomplete\\FilamentGoogleAutocompleteServiceProvider" 84 | ] 85 | } 86 | }, 87 | "minimum-stability": "dev", 88 | "prefer-stable": true 89 | } 90 | -------------------------------------------------------------------------------- /config/filament-google-autocomplete-field.php: -------------------------------------------------------------------------------- 1 | env('GOOGLE_PLACES_API_KEY', ''), 6 | 'verify-ssl' => true, 7 | 'throw-on-errors' => false, 8 | 9 | ]; 10 | -------------------------------------------------------------------------------- /resources/lang/en/filament-google-autocomplete-field.php: -------------------------------------------------------------------------------- 1 | 'Searching location on Google...', 6 | 'autocomplete.search.prompt' => 'Type a location to search using Google', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/lang/it/filament-google-autocomplete-field.php: -------------------------------------------------------------------------------- 1 | 'Ricerca della posizione su Google in corso...', 6 | 'autocomplete.search.prompt' => 'Digita un luogo da cercare tramite Google.', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /src/Concerns/CanFormatGoogleParams.php: -------------------------------------------------------------------------------- 1 | 0) { 14 | $result = array_map(function ($country) { 15 | return sprintf('country:%s|', $country); 16 | }, $countries); 17 | 18 | return rtrim(implode('', $result), '|'); 19 | } 20 | 21 | return null; 22 | } 23 | 24 | public function getFormattedPlaceTypes(string|array $placeTypes): ?string 25 | { 26 | $placeTypes = Arr::wrap($placeTypes); 27 | 28 | if (count($placeTypes) > 0) { 29 | $result = array_map(function ($placeType) { 30 | return sprintf('%s|', $placeType); 31 | }, $placeTypes); 32 | 33 | return rtrim(implode('', $result), '|'); 34 | } 35 | 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Concerns/HasGooglePlaceApi.php: -------------------------------------------------------------------------------- 1 | [ 24 | 'longText' => 'longText', 25 | 'shortText' => 'shortText', 26 | 'googleAddressExtraFieldNames' => [ 27 | 'id', 28 | 'nationalPhoneNumber', 29 | 'internationalPhoneNumber', 30 | 'formattedAddress', 31 | 'displayName', 32 | 'websiteUri', 33 | ], 34 | ], 35 | 'originalApi' => [ 36 | 'longText' => 'long_name', 37 | 'shortText' => 'short_name', 38 | 'googleAddressExtraFieldNames' => [ 39 | 'place_id', 40 | 'formatted_phone_number', 41 | 'international_phone_number', 42 | 'formatted_address', 43 | 'name', 44 | 'website', 45 | ], 46 | ], 47 | ]; 48 | 49 | protected array $currentApiNamingConventions = []; 50 | 51 | protected bool|Closure $placesApiNew = false; 52 | 53 | protected bool|Closure $includePureServiceAreaBusinesses = false; 54 | 55 | protected string|array|Closure|null $countries = null; 56 | 57 | protected array|Closure|null $includedRegionCodes = null; 58 | 59 | protected string|Closure|null $language = null; 60 | 61 | protected string|Closure|null $location = null; 62 | 63 | protected string|array|Closure|null $locationBias = null; 64 | 65 | protected string|Closure|null $locationRestriction = null; 66 | 67 | protected int|Closure|null $offset = null; 68 | 69 | protected string|Closure|null $origin = null; 70 | 71 | protected int|Closure|null $radius = null; 72 | 73 | protected string|Closure|null $region = null; 74 | 75 | protected string|Closure|null $sessionToken = null; 76 | 77 | protected string|array|Closure|null $placeTypes = null; 78 | 79 | protected function getPlaceAutocomplete($search) 80 | { 81 | $this->setGoogleApi(); 82 | 83 | if ($this->placesApiNew) { 84 | return $this->googlePlaces->autocomplete($search, false, ['*'], $this->params); 85 | } 86 | 87 | return $this->googlePlaces->placeAutocomplete($search, $this->params); 88 | } 89 | 90 | protected function getPlace(string $placeId) 91 | { 92 | $this->setGoogleApi(); 93 | 94 | $detailParams = []; 95 | 96 | if ($this->placesApiNew) { 97 | $detailParams['languageCode'] = $this->params['languageCode'] ?? null; 98 | 99 | return $this->googlePlaces->placeDetails($placeId, ['*'], $detailParams); 100 | } 101 | 102 | $detailParams['language'] = $this->params['language'] ?? null; 103 | 104 | return $this->googlePlaces->placeDetails($placeId, $detailParams); 105 | } 106 | 107 | protected function getFormattedApiResults($data): array 108 | { 109 | $response = $data->collect(); 110 | 111 | if ($this->placesApiNew) { 112 | $addressComponents = $response['addressComponents']; 113 | 114 | $latLngFields = [ 115 | 'latitude' => [ 116 | 'long_name' => $response['location']['latitude'], 117 | 'short_name' => $response['location']['latitude'], 118 | ], 119 | 'longitude' => [ 120 | 'long_name' => $response['location']['longitude'], 121 | 'short_name' => $response['location']['longitude'], 122 | ], 123 | ]; 124 | 125 | $extraFields = $response->toArray(); 126 | } else { 127 | $result = $response['result']; 128 | 129 | $addressComponents = $result['address_components']; 130 | 131 | $latLngFields = $result['geometry']['location']; 132 | 133 | $latLngFields = [ 134 | 'latitude' => [ 135 | 'long_name' => $latLngFields['lat'], 136 | 'short_name' => $latLngFields['lat'], 137 | ], 138 | 'longitude' => [ 139 | 'long_name' => $latLngFields['lng'], 140 | 'short_name' => $latLngFields['lng'], 141 | ], 142 | ]; 143 | 144 | $extraFields = $result; 145 | } 146 | 147 | // array map with keys 148 | $addressFields = array_merge(...array_map(function ($key, $item) { 149 | return [ 150 | $item['types'][0] => [ 151 | 'long_name' => $item[$this->currentApiNamingConventions['longText']], 152 | 'short_name' => $item[$this->currentApiNamingConventions['shortText']], 153 | ], 154 | ]; 155 | }, array_keys($addressComponents), $addressComponents)); 156 | 157 | // array map with keys 158 | $extraFields = array_merge(...array_map(function ($key, $item) { 159 | if (in_array($key, $this->currentApiNamingConventions['googleAddressExtraFieldNames'])) { 160 | $item = $key === 'displayName' ? $item['text'] : $item; 161 | 162 | return [ 163 | $key => [ 164 | 'long_name' => $item, 165 | 'short_name' => $item, 166 | ], 167 | ]; 168 | } else { 169 | return []; 170 | } 171 | }, array_keys($extraFields), $extraFields)); 172 | 173 | return array_merge($addressFields, $extraFields, $latLngFields); 174 | } 175 | 176 | public function placesApiNew(bool|Closure $placesApiNew = true): static 177 | { 178 | $this->placesApiNew = $placesApiNew; 179 | 180 | return $this; 181 | } 182 | 183 | public function getPlacesApiNew(): bool 184 | { 185 | return $this->evaluate($this->placesApiNew); 186 | } 187 | 188 | public function includePureServiceAreaBusinesses(bool|Closure $includePureServiceAreaBusinesses = false): static 189 | { 190 | $this->params['includePureServiceAreaBusinesses'] = $includePureServiceAreaBusinesses; 191 | 192 | $this->includePureServiceAreaBusinesses = $includePureServiceAreaBusinesses; 193 | 194 | return $this; 195 | } 196 | 197 | public function getIncludePureServiceAreaBusinesses(): bool 198 | { 199 | return $this->evaluate($this->includePureServiceAreaBusinesses); 200 | } 201 | 202 | protected function setGoogleApi() 203 | { 204 | $googleClass = 'SKAgarwal\GoogleApi\Places\GooglePlaces'; 205 | 206 | $this->currentApiNamingConventions = $this->apiNamingConventions['originalApi']; 207 | 208 | if ($this->placesApiNew) { 209 | $googleClass = 'SKAgarwal\GoogleApi\PlacesNew\GooglePlaces'; 210 | 211 | $this->currentApiNamingConventions = $this->apiNamingConventions['newApi']; 212 | } 213 | 214 | $this->googlePlaces = $googleClass::make( 215 | key: config('filament-google-autocomplete-field.api-key'), 216 | verifySSL: config('filament-google-autocomplete-field.verify-ssl'), 217 | throwOnErrors: config('filament-google-autocomplete-field.throw-on-errors'), 218 | ); 219 | } 220 | 221 | public function countries(array|string|Closure|null $countries): static 222 | { 223 | $countries = Arr::wrap($countries); 224 | 225 | $this->countries = $countries; 226 | 227 | $this->params['components'] = $this->getFormattedCountries($countries); 228 | 229 | return $this; 230 | } 231 | 232 | public function getCountries(): ?string 233 | { 234 | return $this->evaluate($this->countries); 235 | } 236 | 237 | public function includedRegionCodes(array|Closure|null $includedRegionCodes): static 238 | { 239 | $includedRegionCodes = Arr::wrap($includedRegionCodes); 240 | 241 | $this->includedRegionCodes = $includedRegionCodes; 242 | 243 | $this->params['includedRegionCodes'] = $includedRegionCodes; 244 | 245 | return $this; 246 | } 247 | 248 | public function getIncludedRegionCodes(): ?array 249 | { 250 | return $this->evaluate($this->includedRegionCodes); 251 | } 252 | 253 | public function language(string|Closure|null $language): static 254 | { 255 | $this->language = $language; 256 | 257 | if ($this->placesApiNew) { 258 | $this->params['languageCode'] = $language; 259 | } else { 260 | $this->params['language'] = $language; 261 | } 262 | 263 | return $this; 264 | } 265 | 266 | public function getLanguage(): ?string 267 | { 268 | return $this->evaluate($this->language); 269 | } 270 | 271 | public function location(string|Closure|null $location): static 272 | { 273 | $this->location = $location; 274 | 275 | $this->params['location'] = $location; 276 | 277 | return $this; 278 | } 279 | 280 | public function getLocation(): ?string 281 | { 282 | return $this->evaluate($this->location); 283 | } 284 | 285 | public function locationBias(string|array|Closure|null $locationBias): static 286 | { 287 | $this->locationBias = $locationBias; 288 | 289 | if ($this->placesApiNew) { 290 | $this->params['locationBias'] = $locationBias; 291 | } else { 292 | $this->params['locationbias'] = $locationBias; 293 | } 294 | 295 | return $this; 296 | } 297 | 298 | public function getLocationBias(): null|string|array 299 | { 300 | return $this->evaluate($this->locationBias); 301 | } 302 | 303 | public function locationRestriction(string|Closure|null $locationRestriction): static 304 | { 305 | $this->locationRestriction = $locationRestriction; 306 | 307 | if ($this->placesApiNew) { 308 | $this->params['locationRestriction'] = $locationRestriction; 309 | } else { 310 | $this->params['locationrestriction'] = $locationRestriction; 311 | } 312 | 313 | return $this; 314 | } 315 | 316 | public function getLocationRestriction(): ?string 317 | { 318 | return $this->evaluate($this->locationRestriction); 319 | } 320 | 321 | public function offset(int|Closure|null $offset): static 322 | { 323 | $this->offset = $offset; 324 | 325 | if ($this->placesApiNew) { 326 | $this->params['inputOffset'] = $offset; 327 | } else { 328 | $this->params['offset'] = $offset; 329 | } 330 | 331 | return $this; 332 | } 333 | 334 | public function getOffset(): ?int 335 | { 336 | return $this->evaluate($this->offset); 337 | } 338 | 339 | public function origin(string|Closure|null $origin): static 340 | { 341 | $this->origin = $origin; 342 | 343 | $this->params['origin'] = $origin; 344 | 345 | return $this; 346 | } 347 | 348 | public function getOrigin(): ?string 349 | { 350 | return $this->evaluate($this->origin); 351 | } 352 | 353 | public function radius(int|Closure|null $radius): static 354 | { 355 | $this->radius = $radius; 356 | 357 | $this->params['radius'] = $radius; 358 | 359 | return $this; 360 | } 361 | 362 | public function getRadius(): ?int 363 | { 364 | return $this->evaluate($this->radius); 365 | } 366 | 367 | public function region(string|Closure|null $region): static 368 | { 369 | $this->region = $region; 370 | 371 | if ($this->placesApiNew) { 372 | $this->params['regionCode'] = $region; 373 | } else { 374 | $this->params['region'] = $region; 375 | } 376 | 377 | return $this; 378 | } 379 | 380 | public function getRegion(): ?string 381 | { 382 | return $this->evaluate($this->region); 383 | } 384 | 385 | public function sessionToken(string|Closure|null $sessionToken): static 386 | { 387 | $this->sessionToken = $sessionToken; 388 | 389 | if ($this->placesApiNew) { 390 | $this->params['sessionToken'] = $sessionToken; 391 | } else { 392 | $this->params['sessiontoken'] = $sessionToken; 393 | } 394 | 395 | return $this; 396 | } 397 | 398 | public function getSessionToken(): ?string 399 | { 400 | return $this->evaluate($this->sessionToken); 401 | } 402 | 403 | public function placeTypes(array|string|Closure|null $placeTypes): static 404 | { 405 | $placeTypes = Arr::wrap($placeTypes); 406 | 407 | $this->placeTypes = $placeTypes; 408 | 409 | if ($this->placesApiNew) { 410 | $this->params['includedPrimaryTypes'] = $placeTypes; 411 | } else { 412 | $this->params['types'] = $this->getFormattedPlaceTypes($placeTypes); 413 | } 414 | 415 | return $this; 416 | } 417 | 418 | public function getPlaceTypes(): ?string 419 | { 420 | return $this->evaluate($this->placeTypes); 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /src/FilamentGoogleAutocompleteServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('filament-google-autocomplete-field') 14 | ->hasConfigFile() 15 | ->hasTranslations(); 16 | } 17 | 18 | public function packageBooted(): void 19 | { 20 | // 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Forms/Components/GoogleAutocomplete.php: -------------------------------------------------------------------------------- 1 | name($name); 51 | } 52 | 53 | public static function make(string $name): static 54 | { 55 | $static = app(static::class, ['name' => $name]); 56 | $static->configure(); 57 | $static->columnSpanFull(); 58 | 59 | return $static; 60 | } 61 | 62 | /** 63 | * @return array 64 | */ 65 | public function getChildComponents(): array 66 | { 67 | $components = []; 68 | 69 | $components[] = Forms\Components\Select::make($this->getAutocompleteName()) 70 | ->label($this->getAutocompleteLabel()) 71 | ->native(false) 72 | ->dehydrated(false) 73 | ->allowHtml() 74 | ->live() 75 | ->placeholder($this->getAutocompletePlaceholder()) 76 | ->searchDebounce($this->getAutocompleteSearchDebounce()) // 2 seconds 77 | ->searchingMessage(__('filament-google-autocomplete-field::filament-google-autocomplete-field.autocomplete.searching.message')) 78 | ->searchPrompt(__('filament-google-autocomplete-field::filament-google-autocomplete-field.autocomplete.search.prompt')) 79 | ->searchable() 80 | ->hint(new HtmlString(Blade::render(''))) 81 | ->columnSpan($this->getAutocompleteFieldColumnSpan()) 82 | ->getSearchResultsUsing(function (string $search, Set $set): array { 83 | $set($this->getAutocompleteName(), null); 84 | $response = $this->getPlaceAutocomplete($search); 85 | 86 | $result = $response->collect(); 87 | 88 | return $this->getPlaceAutocompleteResult($result); 89 | }) 90 | ->afterStateUpdated(function (?string $state, Set $set) { 91 | if ($state === null) { 92 | foreach ($this->getWithFields() as $field) { 93 | $set($field->getName(), null); 94 | } 95 | 96 | return; 97 | } 98 | 99 | $data = $this->getPlace($state); 100 | 101 | $googleFields = $this->getFormattedApiResults($data); 102 | 103 | foreach ($this->getWithFields() as $field) { 104 | $fieldExtraAttributes = $field->getExtraInputAttributes(); 105 | 106 | $googleFieldName = count($fieldExtraAttributes) > 0 && isset($fieldExtraAttributes['data-google-field']) ? $fieldExtraAttributes['data-google-field'] : $field->getName(); 107 | 108 | $googleFieldValue = count($fieldExtraAttributes) > 0 && isset($fieldExtraAttributes['data-google-value']) ? $fieldExtraAttributes['data-google-value'] : 'long_name'; 109 | 110 | // if the field contains combined values 111 | if (str_contains($googleFieldName, '{')) { 112 | $value = $this->replaceFieldPlaceholders($googleFieldName, $googleFields, $googleFieldValue); 113 | } else { 114 | // bc: Fixes issue with Carson City, NV. No administrative_area_level_2 provided in search result. 115 | $value = ''; 116 | if (isset($googleFields[$googleFieldName][$googleFieldValue])) { 117 | $value = $googleFields[$googleFieldName][$googleFieldValue] ?: ''; 118 | } 119 | } 120 | 121 | $set($field->getName(), $value); 122 | $field->callAfterStateUpdated(); 123 | } 124 | }); 125 | 126 | $addressData = Arr::map( 127 | $this->getWithFields(), 128 | function (Component $component) { 129 | return $component; 130 | } 131 | ); 132 | 133 | $allComponents = array_merge($components, $addressData); 134 | 135 | return [ 136 | Forms\Components\Grid::make($this->getAddressFieldsColumns()) 137 | ->schema( 138 | $allComponents 139 | ), 140 | ]; 141 | } 142 | 143 | protected function replaceFieldPlaceholders($googleField, $googleFields, $googleFieldValue) 144 | { 145 | preg_match_all('/{(.*?)}/', $googleField, $matches); 146 | 147 | foreach ($matches[1] as $item) { 148 | $valueToReplace = isset($googleFields[$item][$googleFieldValue]) ? $googleFields[$item][$googleFieldValue] : ''; 149 | 150 | $googleField = str_ireplace('{'.$item.'}', $valueToReplace, $googleField); 151 | } 152 | 153 | return $googleField; 154 | } 155 | 156 | protected function getPlaceAutocompleteResult($result) 157 | { 158 | if ($this->placesApiNew) { 159 | if (isset($result['suggestions']) && ! empty($result['suggestions'])) { 160 | $searchResults = collect($result['suggestions'])->mapWithKeys(function (array $item, int $key) { 161 | return [$item['placePrediction']['placeId'] => $item['placePrediction']['text']['text']]; 162 | }); 163 | 164 | return $searchResults->toArray(); 165 | } 166 | } else { 167 | if (! empty($result['predictions'])) { 168 | $searchResults = collect($result['predictions'])->mapWithKeys(function (array $item, int $key) { 169 | return [$item['place_id'] => $item['description']]; 170 | }); 171 | 172 | return $searchResults->toArray(); 173 | } 174 | } 175 | 176 | return []; 177 | } 178 | 179 | public function withFields(null|array|string|\Closure $fields): static 180 | { 181 | $this->withFields = $fields; 182 | 183 | return $this; 184 | } 185 | 186 | public function getWithFields(): ?array 187 | { 188 | if (empty($this->withFields)) { 189 | return [ 190 | Forms\Components\TextInput::make('address') 191 | ->extraInputAttributes([ 192 | 'data-google-field' => '{street_number} {route}, {sublocality_level_1}', 193 | ]), 194 | Forms\Components\TextInput::make('city') 195 | ->extraInputAttributes([ 196 | 'data-google-field' => 'locality', 197 | ]), 198 | Forms\Components\TextInput::make('country'), 199 | Forms\Components\TextInput::make('zip') 200 | ->extraInputAttributes([ 201 | 'data-google-field' => 'postal_code', 202 | ]), 203 | ]; 204 | } 205 | 206 | return $this->evaluate($this->withFields); 207 | } 208 | 209 | public function autocompleteFieldColumnSpan(string|\Closure $autocompleteFieldColumnSpan = 'full'): static 210 | { 211 | $this->autocompleteFieldColumnSpan = $autocompleteFieldColumnSpan; 212 | 213 | $this->params['autocompleteFieldColumnSpan'] = $autocompleteFieldColumnSpan; 214 | 215 | return $this; 216 | } 217 | 218 | public function getAutocompleteFieldColumnSpan(): ?string 219 | { 220 | return $this->evaluate($this->autocompleteFieldColumnSpan); 221 | } 222 | 223 | public function addressFieldsColumns(null|array|string|\Closure $addressFieldsColumns): static 224 | { 225 | $this->addressFieldsColumns = $addressFieldsColumns; 226 | 227 | return $this; 228 | } 229 | 230 | public function getAddressFieldsColumns(): ?array 231 | { 232 | if (empty($this->addressFieldsColumns)) { 233 | return [ 234 | 'default' => 1, 235 | 'sm' => 2, 236 | ]; 237 | } 238 | 239 | return $this->evaluate($this->addressFieldsColumns); 240 | } 241 | 242 | public function autocompleteSearchDebounce(int|\Closure $autocompleteSearchDebounce = 2000): static 243 | { 244 | $this->autocompleteSearchDebounce = $autocompleteSearchDebounce; 245 | 246 | $this->params['autocompleteSearchDebounce'] = $autocompleteSearchDebounce; 247 | 248 | return $this; 249 | } 250 | 251 | public function getAutocompleteSearchDebounce(): ?int 252 | { 253 | return $this->evaluate($this->autocompleteSearchDebounce); 254 | } 255 | 256 | public function autocompleteLabel(string|Closure|null $label): static 257 | { 258 | $this->autocompleteLabel = $label; 259 | 260 | return $this; 261 | } 262 | 263 | protected function getAutocompleteLabel(): string 264 | { 265 | return $this->evaluate($this->autocompleteLabel) ?? $this->getLabel(); 266 | } 267 | 268 | public function autocompleteName(string|Closure|null $name): static 269 | { 270 | $this->autocompleteName = $name; 271 | 272 | return $this; 273 | } 274 | 275 | protected function getAutocompleteName(): string 276 | { 277 | return $this->evaluate($this->autocompleteName) ?? 'google_autocomplete_'.$this->name; 278 | } 279 | 280 | public function autocompletePlaceholder(string|Closure|null $placeholder): static 281 | { 282 | $this->autocompletePlaceholder = $placeholder; 283 | 284 | return $this; 285 | } 286 | 287 | protected function getAutocompletePlaceholder(): string 288 | { 289 | return $this->evaluate($this->autocompletePlaceholder) ?? __('Select an option'); 290 | } 291 | } 292 | --------------------------------------------------------------------------------