diff --git a/CHANGES.md b/CHANGES.md
index dda0aae..26925fe 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,8 +1,13 @@
+* version 2.5.0 - 2026-08-27
+ * Fix: `reverse()` now builds its own `q=lat,lng` query instead of stringifying the location object
+ * Fix: `reverseQueryParams` was declared but never used. Now used, added tests
+
* version 2.4.2 - unreleased
+ * Demo page: the map click handler uses `reverse()` rather than hand-building a coordinate string
* Fix: demo page escapes HTML before inserting it into the DOM
* Code cleanup: replace deprecated `__dirname` in the vite config
* Code cleanup: the Github Action workflow now only requests read permissions
- * Node v18 is no longer supported
+ * Build process: Node v18 is no longer supported
* created a CHANGED.md file and backfilled
* NPM package updates: vite, js-yaml, undici, postcss, dev dependencies
diff --git a/README.md b/README.md
index 3b4a984..0c9e92f 100644
--- a/README.md
+++ b/README.md
@@ -125,13 +125,42 @@ var options = {
geohash: "annotations.geohash",
what3words: "annotations.what3words",
addressComponents: "components"
- } //if additional attributes from OpenCage search API should be added to the result
+ }, //if additional attributes from OpenCage search API should be added to the result
+ geocodingQueryParams: {}, // extra query parameters sent with forward geocoding requests
+ reverseQueryParams: {} // extra query parameters sent with reverse geocoding requests
};
var control = L.Control.openCageGeocoding(options).addTo(map);
```
+Any [OpenCage API parameter](https://opencagedata.com/api#forward-opt) can be
+passed via `geocodingQueryParams` and `reverseQueryParams`, for example
+`{ language: 'de', countrycode: 'de' }`.
+
+## Reverse geocoding
+
+The geocoder can also be used directly, without the control, to turn coordinates
+into a place name or addresss:
+
+```javascript
+var geocoder = L.Control.OpenCageGeocoding.geocoder({
+ key: 'your-api-key-here',
+});
+
+map.on('click', function (e) {
+ // the second argument (scale) is ignored
+ geocoder.reverse(e.latlng, map.getZoom(), function (results) {
+ if (results[0]) {
+ L.marker(results[0].center).addTo(map);
+ }
+ });
+});
+```
+
+`reverse()` accepts anything `L.latLng()` does: `L.LatLng` object, `[lat, lng]`
+array or `{lat, lng}`. It throws a `TypeError` for anything else.
+
## Contributing
See `CONTRIBUTING.md` file.
diff --git a/demo/demo.html b/demo/demo.html
index 3c0ac7a..3024264 100644
--- a/demo/demo.html
+++ b/demo/demo.html
@@ -140,8 +140,8 @@
✨ Modernization Features
}
map.on('click', function (e) {
- const query = e.latlng.lat.toString() + ',' + e.latlng.lng.toString();
- geocoder.geocode(query, function (results) {
+ // scale is ignored by the OpenCage geocoder, but IGeocoder requires it
+ geocoder.reverse(e.latlng, map.getZoom(), function (results) {
const r = results[0];
if (r) {
console.log('📌 Reverse geocoding result:', r);
diff --git a/dist/js/L.Control.OpenCageGeocoding.dev.js b/dist/js/L.Control.OpenCageGeocoding.dev.js
index 52c89ef..feeecc0 100644
--- a/dist/js/L.Control.OpenCageGeocoding.dev.js
+++ b/dist/js/L.Control.OpenCageGeocoding.dev.js
@@ -1,5 +1,5 @@
/**
- * OpenCage Data Geocoding Control v2.4.2 - 2026-08-05
+ * OpenCage Data Geocoding Control v2.5.0 - 2026-08-27
* Copyright (c) 2026, OpenCage GmbH
* support@opencagedata.com
* https://opencagedata.com
@@ -68,20 +68,39 @@
...proximity,
...this.options.geocodingQueryParams
};
- this._makeRequest(this.options.serviceUrl, params, (data) => {
- const results = this._processResults(data);
- callback.call(context, results);
- });
+ this._request(params, callback, context);
}
/**
* Reverse geocode a location
- * @param {Object} location - Location object with lat/lng
- * @param {number} scale - Scale parameter (unused in current implementation)
+ *
+ * Use `reverseQueryParams` to add parameters here. `limit` is omitted
+ * because the API returns at most one reverse geocoding result.
+ *
+ * @param {L.LatLng|Array|Object} location - Anything L.latLng() accepts:
+ * an L.LatLng, [lat, lng] or {lat, lng}
+ * @param {number} scale - Ignored
* @param {Function} callback - Callback function to handle results
* @param {Object} context - Context object
*/
reverse(location, scale, callback, context) {
- this.geocode(location, callback, context);
+ const latLng = leaflet.default.latLng(location);
+ if (!latLng) throw new TypeError(`Invalid location for reverse geocoding: ${JSON.stringify(location)}. Expected an L.LatLng, [lat, lng] or {lat, lng}`);
+ const params = {
+ q: `${latLng.lat},${latLng.lng}`,
+ key: this.options.key,
+ ...this.options.reverseQueryParams
+ };
+ this._request(params, callback, context);
+ }
+ /**
+ * Send a request and deliver the processed results to the callback
+ * @private
+ */
+ _request(params, callback, context) {
+ this._makeRequest(this.options.serviceUrl, params, (data) => {
+ const results = this._processResults(data);
+ callback.call(context, results);
+ });
}
/**
* Get proximity parameter from map center
diff --git a/dist/js/L.Control.OpenCageGeocoding.dev.js.map b/dist/js/L.Control.OpenCageGeocoding.dev.js.map
index 472ab0c..f94eab3 100644
--- a/dist/js/L.Control.OpenCageGeocoding.dev.js.map
+++ b/dist/js/L.Control.OpenCageGeocoding.dev.js.map
@@ -1 +1 @@
-{"version":3,"file":"L.Control.OpenCageGeocoding.dev.js","names":["L","L"],"sources":["../../src/js/geocoder.js","../../src/js/L.Control.OpenCageGeocoding.js"],"sourcesContent":["import L from 'leaflet';\n\n/**\n * Geocoder class for OpenCage API interactions\n */\nexport class OpenCageGeocoder {\n constructor(options = {}) {\n this.options = {\n serviceUrl: 'https://api.opencagedata.com/geocode/v1/json/',\n geocodingQueryParams: {},\n reverseQueryParams: {},\n key: '',\n limit: 5,\n ...options,\n };\n }\n\n /**\n * Geocode a query string\n * @param {string} query - The search query\n * @param {Function} callback - Callback function to handle results\n * @param {Object} context - Context object (typically the control instance)\n */\n geocode(query, callback, context) {\n const proximity = this._getProximity(context);\n\n const params = {\n q: query,\n limit: this.options.limit,\n key: this.options.key,\n ...proximity,\n ...this.options.geocodingQueryParams,\n };\n\n this._makeRequest(this.options.serviceUrl, params, (data) => {\n const results = this._processResults(data);\n callback.call(context, results);\n });\n }\n\n /**\n * Reverse geocode a location\n * @param {Object} location - Location object with lat/lng\n * @param {number} scale - Scale parameter (unused in current implementation)\n * @param {Function} callback - Callback function to handle results\n * @param {Object} context - Context object\n */\n reverse(location, scale, callback, context) {\n this.geocode(location, callback, context);\n }\n\n /**\n * Get proximity parameter from map center\n * @private\n */\n _getProximity(context) {\n const proximity = {};\n if (context && context._map && context._map.getCenter()) {\n const center = context._map.getCenter();\n proximity.proximity = center.lat + ',' + center.lng;\n }\n return proximity;\n }\n\n /**\n * Process API results into standardized format\n * @private\n */\n _processResults(data) {\n const results = [];\n\n for (let i = data.results.length - 1; i >= 0; i--) {\n results[i] = {\n name: data.results[i].formatted,\n center: L.latLng(\n data.results[i].geometry.lat,\n data.results[i].geometry.lng\n ),\n };\n\n if (data.results[i].bounds) {\n results[i].bounds = L.latLngBounds(\n [\n data.results[i].bounds.southwest.lat,\n data.results[i].bounds.southwest.lng,\n ],\n [\n data.results[i].bounds.northeast.lat,\n data.results[i].bounds.northeast.lng,\n ]\n );\n }\n\n if (this.options.resultExtension) {\n this._addResultExtensions(results[i], data.results[i]);\n }\n }\n\n return results;\n }\n\n /**\n * Add result extensions based on configuration\n * @private\n */\n _addResultExtensions(result, apiResult) {\n const resultExtObj = this.options.resultExtension;\n const resultExtKeys = Object.keys(resultExtObj);\n\n for (let j = resultExtKeys.length - 1; j >= 0; j--) {\n const key = resultExtKeys[j];\n let resultAttr = apiResult;\n\n const attrPathKeys = resultExtObj[key].split('.');\n for (let k = 0; k < attrPathKeys.length; k++) {\n const keypath = attrPathKeys[k];\n if (resultAttr[keypath]) {\n resultAttr = resultAttr[keypath];\n } else {\n resultAttr = undefined;\n break;\n }\n }\n result[key] = resultAttr;\n }\n }\n\n _makeRequest(url, params, callback) {\n const queryString = new URLSearchParams(params).toString();\n fetch(`${url}?${queryString}`)\n .then((response) => (response.ok ? response.json() : { results: [] }))\n .then((data) => callback(data))\n .catch(() => callback({ results: [] }));\n }\n}\n\n/**\n * Factory function for creating geocoder instances\n */\nexport function createGeocoder(options) {\n return new OpenCageGeocoder(options);\n}\n","import L from 'leaflet';\nimport { OpenCageGeocoder } from './geocoder.js';\nimport '../css/L.Control.OpenCageGeocoding.css';\n\n/**\n * OpenCage Geocoding Control for Leaflet\n */\nexport class OpenCageGeocodingControl extends L.Control {\n constructor(options = {}) {\n super(options);\n this.options = {\n showResultIcons: false,\n collapsed: true,\n expand: 'click',\n position: 'topright',\n placeholder: 'Search...',\n errorMessage: 'Nothing found.',\n key: '',\n onResultClick: undefined,\n addResultToMap: true,\n limit: 5,\n ...options,\n };\n\n this._callbackId = 0;\n\n if (!this.options.geocoder) {\n this.options.geocoder = new OpenCageGeocoder(this.options);\n }\n }\n onAdd(map) {\n const className = 'leaflet-control-opencage-geocoding';\n const container = L.DomUtil.create('div', className);\n const icon = L.DomUtil.create(\n 'div',\n 'leaflet-control-opencage-geocoding-icon',\n container\n );\n const form = L.DomUtil.create('form', className + '-form', container);\n this._form = form;\n\n this._map = map;\n this._container = container;\n\n const input = L.DomUtil.create('input');\n this._input = input;\n input.type = 'text';\n input.placeholder = this.options.placeholder;\n\n L.DomEvent.addListener(input, 'keydown', this._keydown, this);\n\n this._errorElement = document.createElement('div');\n this._errorElement.className = className + '-form-no-error';\n this._errorElement.textContent = this.options.errorMessage;\n\n this._alts = L.DomUtil.create(\n 'ul',\n className +\n '-alternatives leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n\n form.appendChild(input);\n form.appendChild(this._errorElement);\n container.appendChild(this._alts);\n\n L.DomEvent.addListener(form, 'submit', this._geocode, this);\n\n if (this.options.collapsed) {\n if (this.options.expand === 'click') {\n L.DomEvent.addListener(\n icon,\n 'click',\n (e) => {\n if (e.button === 0 && e.detail !== 2) {\n this._toggle();\n }\n },\n this\n );\n } else {\n L.DomEvent.addListener(icon, 'mouseover', this._expand, this);\n L.DomEvent.addListener(icon, 'mouseout', this._collapse, this);\n this._map.on('movestart', this._collapse, this);\n }\n } else {\n this._expand();\n }\n\n L.DomEvent.disableClickPropagation(container);\n\n return container;\n }\n\n /**\n * Handle geocoding results\n */\n _geocodeResult(results) {\n L.DomUtil.removeClass(\n this._container,\n 'leaflet-control-opencage-geocoding-spinner'\n );\n\n if (results.length === 1) {\n this._geocodeResultSelected(results[0]);\n } else if (results.length > 0) {\n this._alts.innerText = '';\n this._results = results;\n L.DomUtil.removeClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n for (let i = 0; i < results.length; i++) {\n this._alts.appendChild(this._createAlt(results[i], i));\n }\n } else {\n L.DomUtil.addClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n }\n\n /**\n * Mark geocode result on map\n */\n markGeocode(result) {\n if (result.bounds) {\n // console.log(`fit bounds`, result.bounds);\n this._map.fitBounds(result.bounds);\n } else {\n // console.log(`pan to`, result.center);\n this._map.panTo(result.center);\n }\n\n if (this._geocodeMarker) {\n this._map.removeLayer(this._geocodeMarker);\n }\n\n // .bindPopup treats the input as HTML\n const popupEl = document.createElement('div');\n popupEl.textContent = result.name;\n\n this._geocodeMarker = new L.Marker(result.center)\n .bindPopup(popupEl)\n .addTo(this._map)\n .openPopup();\n\n return this;\n }\n\n /**\n * Perform geocoding\n */\n _geocode(event) {\n L.DomEvent.preventDefault(event);\n\n L.DomUtil.addClass(\n this._container,\n 'leaflet-control-opencage-geocoding-spinner'\n );\n this._clearResults();\n this.options.geocoder.geocode(this._input.value, this._geocodeResult, this);\n\n return false;\n }\n\n /**\n * Handle selected geocoding result\n */\n _geocodeResultSelected(result) {\n if (this.options.collapsed) {\n this._collapse();\n } else {\n this._clearResults();\n }\n\n if (\n this.options.onResultClick &&\n typeof this.options.onResultClick === 'function'\n ) {\n this.options.onResultClick(result);\n }\n\n if (this.options.addResultToMap) {\n this.markGeocode(result);\n }\n }\n\n /**\n * Toggle control expansion\n */\n _toggle() {\n if (\n this._container.className.indexOf(\n 'leaflet-control-opencage-geocoding-expanded'\n ) >= 0\n ) {\n this._collapse();\n } else {\n this._expand();\n }\n }\n\n /**\n * Expand the control\n */\n _expand() {\n L.DomUtil.addClass(\n this._container,\n 'leaflet-control-opencage-geocoding-expanded'\n );\n this._input.select();\n }\n\n /**\n * Collapse the control\n */\n _collapse() {\n this._container.className = this._container.className.replace(\n ' leaflet-control-opencage-geocoding-expanded',\n ''\n );\n L.DomUtil.addClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n L.DomUtil.removeClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n\n /**\n * Clear results display\n */\n _clearResults() {\n L.DomUtil.addClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n this._selection = null;\n L.DomUtil.removeClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n\n /**\n * Create alternative result element\n */\n _createAlt(result, index) {\n const li = document.createElement('li');\n const a = document.createElement('a');\n a.href = '#';\n a.dataset.resultIndex = index;\n\n if (this.options.showResultIcons && result.icon) {\n try {\n const u = new URL(result.icon, window.location.href);\n if (u.protocol === 'http:' || u.protocol === 'https:') {\n const img = document.createElement('img');\n img.src = u.href;\n img.alt = '';\n a.appendChild(img);\n }\n } catch {\n // ignore invalid URL\n }\n }\n\n a.appendChild(document.createTextNode(result.name));\n li.appendChild(a);\n\n L.DomEvent.addListener(\n li,\n 'click',\n () => {\n this._geocodeResultSelected(result);\n },\n this\n );\n\n return li;\n }\n\n /**\n * Handle keyboard navigation\n */\n _keydown(e) {\n const select = (dir) => {\n if (this._selection) {\n L.DomUtil.removeClass(\n this._selection.firstChild,\n 'leaflet-control-opencage-geocoding-selected'\n );\n this._selection =\n this._selection[dir > 0 ? 'nextSibling' : 'previousSibling'];\n }\n\n if (!this._selection) {\n this._selection = this._alts[dir > 0 ? 'firstChild' : 'lastChild'];\n }\n\n if (this._selection) {\n L.DomUtil.addClass(\n this._selection.firstChild,\n 'leaflet-control-opencage-geocoding-selected'\n );\n }\n };\n\n switch (e.keyCode) {\n case 38: // Up\n select(-1);\n L.DomEvent.preventDefault(e);\n break;\n case 40: // Down\n select(1);\n L.DomEvent.preventDefault(e);\n break;\n case 13: // Enter\n if (this._selection) {\n const index = parseInt(\n this._selection.firstChild.getAttribute('data-result-index'),\n 10\n );\n this._geocodeResultSelected(this._results[index]);\n this._clearResults();\n L.DomEvent.preventDefault(e);\n }\n break;\n }\n return true;\n }\n}\n\n// Factory function for backwards compatibility\nconst openCageGeocoding = (options) => {\n return new OpenCageGeocodingControl(options);\n};\n\n// Attach geocoder to the control class\nOpenCageGeocodingControl.Geocoder = OpenCageGeocoder;\nOpenCageGeocodingControl.geocoder = (options) => {\n return new OpenCageGeocoder(options);\n};\n\n// Attach to L.Control for global usage\nObject.assign(L.Control, {\n OpenCageGeocoding: OpenCageGeocodingControl,\n openCageGeocoding: openCageGeocoding,\n});\n\n// Export for ES modules\nexport { OpenCageGeocoder, openCageGeocoding };\nexport default OpenCageGeocodingControl;\n"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"L.Control.OpenCageGeocoding.dev.js","names":["L","L"],"sources":["../../src/js/geocoder.js","../../src/js/L.Control.OpenCageGeocoding.js"],"sourcesContent":["import L from 'leaflet';\n\n/**\n * Geocoder class for OpenCage API interactions\n */\nexport class OpenCageGeocoder {\n constructor(options = {}) {\n this.options = {\n serviceUrl: 'https://api.opencagedata.com/geocode/v1/json/',\n geocodingQueryParams: {},\n reverseQueryParams: {},\n key: '',\n limit: 5,\n ...options,\n };\n }\n\n /**\n * Geocode a query string\n * @param {string} query - The search query\n * @param {Function} callback - Callback function to handle results\n * @param {Object} context - Context object (typically the control instance)\n */\n geocode(query, callback, context) {\n const proximity = this._getProximity(context);\n\n const params = {\n q: query,\n limit: this.options.limit,\n key: this.options.key,\n ...proximity,\n ...this.options.geocodingQueryParams,\n };\n\n this._request(params, callback, context);\n }\n\n /**\n * Reverse geocode a location\n *\n * Use `reverseQueryParams` to add parameters here. `limit` is omitted\n * because the API returns at most one reverse geocoding result.\n *\n * @param {L.LatLng|Array|Object} location - Anything L.latLng() accepts:\n * an L.LatLng, [lat, lng] or {lat, lng}\n * @param {number} scale - Ignored\n * @param {Function} callback - Callback function to handle results\n * @param {Object} context - Context object\n */\n reverse(location, scale, callback, context) {\n const latLng = L.latLng(location);\n\n if (!latLng) {\n throw new TypeError(\n `Invalid location for reverse geocoding: ${JSON.stringify(location)}. ` +\n 'Expected an L.LatLng, [lat, lng] or {lat, lng}'\n );\n }\n\n const params = {\n q: `${latLng.lat},${latLng.lng}`,\n key: this.options.key,\n ...this.options.reverseQueryParams,\n };\n\n this._request(params, callback, context);\n }\n\n /**\n * Send a request and deliver the processed results to the callback\n * @private\n */\n _request(params, callback, context) {\n this._makeRequest(this.options.serviceUrl, params, (data) => {\n const results = this._processResults(data);\n callback.call(context, results);\n });\n }\n\n /**\n * Get proximity parameter from map center\n * @private\n */\n _getProximity(context) {\n const proximity = {};\n if (context && context._map && context._map.getCenter()) {\n const center = context._map.getCenter();\n proximity.proximity = center.lat + ',' + center.lng;\n }\n return proximity;\n }\n\n /**\n * Process API results into standardized format\n * @private\n */\n _processResults(data) {\n const results = [];\n\n for (let i = data.results.length - 1; i >= 0; i--) {\n results[i] = {\n name: data.results[i].formatted,\n center: L.latLng(\n data.results[i].geometry.lat,\n data.results[i].geometry.lng\n ),\n };\n\n if (data.results[i].bounds) {\n results[i].bounds = L.latLngBounds(\n [\n data.results[i].bounds.southwest.lat,\n data.results[i].bounds.southwest.lng,\n ],\n [\n data.results[i].bounds.northeast.lat,\n data.results[i].bounds.northeast.lng,\n ]\n );\n }\n\n if (this.options.resultExtension) {\n this._addResultExtensions(results[i], data.results[i]);\n }\n }\n\n return results;\n }\n\n /**\n * Add result extensions based on configuration\n * @private\n */\n _addResultExtensions(result, apiResult) {\n const resultExtObj = this.options.resultExtension;\n const resultExtKeys = Object.keys(resultExtObj);\n\n for (let j = resultExtKeys.length - 1; j >= 0; j--) {\n const key = resultExtKeys[j];\n let resultAttr = apiResult;\n\n const attrPathKeys = resultExtObj[key].split('.');\n for (let k = 0; k < attrPathKeys.length; k++) {\n const keypath = attrPathKeys[k];\n if (resultAttr[keypath]) {\n resultAttr = resultAttr[keypath];\n } else {\n resultAttr = undefined;\n break;\n }\n }\n result[key] = resultAttr;\n }\n }\n\n _makeRequest(url, params, callback) {\n const queryString = new URLSearchParams(params).toString();\n fetch(`${url}?${queryString}`)\n .then((response) => (response.ok ? response.json() : { results: [] }))\n .then((data) => callback(data))\n .catch(() => callback({ results: [] }));\n }\n}\n\n/**\n * Factory function for creating geocoder instances\n */\nexport function createGeocoder(options) {\n return new OpenCageGeocoder(options);\n}\n","import L from 'leaflet';\nimport { OpenCageGeocoder } from './geocoder.js';\nimport '../css/L.Control.OpenCageGeocoding.css';\n\n/**\n * OpenCage Geocoding Control for Leaflet\n */\nexport class OpenCageGeocodingControl extends L.Control {\n constructor(options = {}) {\n super(options);\n this.options = {\n showResultIcons: false,\n collapsed: true,\n expand: 'click',\n position: 'topright',\n placeholder: 'Search...',\n errorMessage: 'Nothing found.',\n key: '',\n onResultClick: undefined,\n addResultToMap: true,\n limit: 5,\n ...options,\n };\n\n this._callbackId = 0;\n\n if (!this.options.geocoder) {\n this.options.geocoder = new OpenCageGeocoder(this.options);\n }\n }\n onAdd(map) {\n const className = 'leaflet-control-opencage-geocoding';\n const container = L.DomUtil.create('div', className);\n const icon = L.DomUtil.create(\n 'div',\n 'leaflet-control-opencage-geocoding-icon',\n container\n );\n const form = L.DomUtil.create('form', className + '-form', container);\n this._form = form;\n\n this._map = map;\n this._container = container;\n\n const input = L.DomUtil.create('input');\n this._input = input;\n input.type = 'text';\n input.placeholder = this.options.placeholder;\n\n L.DomEvent.addListener(input, 'keydown', this._keydown, this);\n\n this._errorElement = document.createElement('div');\n this._errorElement.className = className + '-form-no-error';\n this._errorElement.textContent = this.options.errorMessage;\n\n this._alts = L.DomUtil.create(\n 'ul',\n className +\n '-alternatives leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n\n form.appendChild(input);\n form.appendChild(this._errorElement);\n container.appendChild(this._alts);\n\n L.DomEvent.addListener(form, 'submit', this._geocode, this);\n\n if (this.options.collapsed) {\n if (this.options.expand === 'click') {\n L.DomEvent.addListener(\n icon,\n 'click',\n (e) => {\n if (e.button === 0 && e.detail !== 2) {\n this._toggle();\n }\n },\n this\n );\n } else {\n L.DomEvent.addListener(icon, 'mouseover', this._expand, this);\n L.DomEvent.addListener(icon, 'mouseout', this._collapse, this);\n this._map.on('movestart', this._collapse, this);\n }\n } else {\n this._expand();\n }\n\n L.DomEvent.disableClickPropagation(container);\n\n return container;\n }\n\n /**\n * Handle geocoding results\n */\n _geocodeResult(results) {\n L.DomUtil.removeClass(\n this._container,\n 'leaflet-control-opencage-geocoding-spinner'\n );\n\n if (results.length === 1) {\n this._geocodeResultSelected(results[0]);\n } else if (results.length > 0) {\n this._alts.innerText = '';\n this._results = results;\n L.DomUtil.removeClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n for (let i = 0; i < results.length; i++) {\n this._alts.appendChild(this._createAlt(results[i], i));\n }\n } else {\n L.DomUtil.addClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n }\n\n /**\n * Mark geocode result on map\n */\n markGeocode(result) {\n if (result.bounds) {\n // console.log(`fit bounds`, result.bounds);\n this._map.fitBounds(result.bounds);\n } else {\n // console.log(`pan to`, result.center);\n this._map.panTo(result.center);\n }\n\n if (this._geocodeMarker) {\n this._map.removeLayer(this._geocodeMarker);\n }\n\n // .bindPopup treats the input as HTML\n const popupEl = document.createElement('div');\n popupEl.textContent = result.name;\n\n this._geocodeMarker = new L.Marker(result.center)\n .bindPopup(popupEl)\n .addTo(this._map)\n .openPopup();\n\n return this;\n }\n\n /**\n * Perform geocoding\n */\n _geocode(event) {\n L.DomEvent.preventDefault(event);\n\n L.DomUtil.addClass(\n this._container,\n 'leaflet-control-opencage-geocoding-spinner'\n );\n this._clearResults();\n this.options.geocoder.geocode(this._input.value, this._geocodeResult, this);\n\n return false;\n }\n\n /**\n * Handle selected geocoding result\n */\n _geocodeResultSelected(result) {\n if (this.options.collapsed) {\n this._collapse();\n } else {\n this._clearResults();\n }\n\n if (\n this.options.onResultClick &&\n typeof this.options.onResultClick === 'function'\n ) {\n this.options.onResultClick(result);\n }\n\n if (this.options.addResultToMap) {\n this.markGeocode(result);\n }\n }\n\n /**\n * Toggle control expansion\n */\n _toggle() {\n if (\n this._container.className.indexOf(\n 'leaflet-control-opencage-geocoding-expanded'\n ) >= 0\n ) {\n this._collapse();\n } else {\n this._expand();\n }\n }\n\n /**\n * Expand the control\n */\n _expand() {\n L.DomUtil.addClass(\n this._container,\n 'leaflet-control-opencage-geocoding-expanded'\n );\n this._input.select();\n }\n\n /**\n * Collapse the control\n */\n _collapse() {\n this._container.className = this._container.className.replace(\n ' leaflet-control-opencage-geocoding-expanded',\n ''\n );\n L.DomUtil.addClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n L.DomUtil.removeClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n\n /**\n * Clear results display\n */\n _clearResults() {\n L.DomUtil.addClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n this._selection = null;\n L.DomUtil.removeClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n\n /**\n * Create alternative result element\n */\n _createAlt(result, index) {\n const li = document.createElement('li');\n const a = document.createElement('a');\n a.href = '#';\n a.dataset.resultIndex = index;\n\n if (this.options.showResultIcons && result.icon) {\n try {\n const u = new URL(result.icon, window.location.href);\n if (u.protocol === 'http:' || u.protocol === 'https:') {\n const img = document.createElement('img');\n img.src = u.href;\n img.alt = '';\n a.appendChild(img);\n }\n } catch {\n // ignore invalid URL\n }\n }\n\n a.appendChild(document.createTextNode(result.name));\n li.appendChild(a);\n\n L.DomEvent.addListener(\n li,\n 'click',\n () => {\n this._geocodeResultSelected(result);\n },\n this\n );\n\n return li;\n }\n\n /**\n * Handle keyboard navigation\n */\n _keydown(e) {\n const select = (dir) => {\n if (this._selection) {\n L.DomUtil.removeClass(\n this._selection.firstChild,\n 'leaflet-control-opencage-geocoding-selected'\n );\n this._selection =\n this._selection[dir > 0 ? 'nextSibling' : 'previousSibling'];\n }\n\n if (!this._selection) {\n this._selection = this._alts[dir > 0 ? 'firstChild' : 'lastChild'];\n }\n\n if (this._selection) {\n L.DomUtil.addClass(\n this._selection.firstChild,\n 'leaflet-control-opencage-geocoding-selected'\n );\n }\n };\n\n switch (e.keyCode) {\n case 38: // Up\n select(-1);\n L.DomEvent.preventDefault(e);\n break;\n case 40: // Down\n select(1);\n L.DomEvent.preventDefault(e);\n break;\n case 13: // Enter\n if (this._selection) {\n const index = parseInt(\n this._selection.firstChild.getAttribute('data-result-index'),\n 10\n );\n this._geocodeResultSelected(this._results[index]);\n this._clearResults();\n L.DomEvent.preventDefault(e);\n }\n break;\n }\n return true;\n }\n}\n\n// Factory function for backwards compatibility\nconst openCageGeocoding = (options) => {\n return new OpenCageGeocodingControl(options);\n};\n\n// Attach geocoder to the control class\nOpenCageGeocodingControl.Geocoder = OpenCageGeocoder;\nOpenCageGeocodingControl.geocoder = (options) => {\n return new OpenCageGeocoder(options);\n};\n\n// Attach to L.Control for global usage\nObject.assign(L.Control, {\n OpenCageGeocoding: OpenCageGeocodingControl,\n openCageGeocoding: openCageGeocoding,\n});\n\n// Export for ES modules\nexport { OpenCageGeocoder, openCageGeocoding };\nexport default OpenCageGeocodingControl;\n"],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/js/L.Control.OpenCageGeocoding.esm.js b/dist/js/L.Control.OpenCageGeocoding.esm.js
index b88e064..20dd9ab 100644
--- a/dist/js/L.Control.OpenCageGeocoding.esm.js
+++ b/dist/js/L.Control.OpenCageGeocoding.esm.js
@@ -1,5 +1,5 @@
/**
- * OpenCage Data Geocoding Control v2.4.2 - 2026-08-05
+ * OpenCage Data Geocoding Control v2.5.0 - 2026-08-27
* Copyright (c) 2026, OpenCage GmbH
* support@opencagedata.com
* https://opencagedata.com
@@ -39,20 +39,39 @@ var OpenCageGeocoder = class {
...proximity,
...this.options.geocodingQueryParams
};
- this._makeRequest(this.options.serviceUrl, params, (data) => {
- const results = this._processResults(data);
- callback.call(context, results);
- });
+ this._request(params, callback, context);
}
/**
* Reverse geocode a location
- * @param {Object} location - Location object with lat/lng
- * @param {number} scale - Scale parameter (unused in current implementation)
+ *
+ * Use `reverseQueryParams` to add parameters here. `limit` is omitted
+ * because the API returns at most one reverse geocoding result.
+ *
+ * @param {L.LatLng|Array|Object} location - Anything L.latLng() accepts:
+ * an L.LatLng, [lat, lng] or {lat, lng}
+ * @param {number} scale - Ignored
* @param {Function} callback - Callback function to handle results
* @param {Object} context - Context object
*/
reverse(location, scale, callback, context) {
- this.geocode(location, callback, context);
+ const latLng = L.latLng(location);
+ if (!latLng) throw new TypeError(`Invalid location for reverse geocoding: ${JSON.stringify(location)}. Expected an L.LatLng, [lat, lng] or {lat, lng}`);
+ const params = {
+ q: `${latLng.lat},${latLng.lng}`,
+ key: this.options.key,
+ ...this.options.reverseQueryParams
+ };
+ this._request(params, callback, context);
+ }
+ /**
+ * Send a request and deliver the processed results to the callback
+ * @private
+ */
+ _request(params, callback, context) {
+ this._makeRequest(this.options.serviceUrl, params, (data) => {
+ const results = this._processResults(data);
+ callback.call(context, results);
+ });
}
/**
* Get proximity parameter from map center
diff --git a/dist/js/L.Control.OpenCageGeocoding.esm.js.map b/dist/js/L.Control.OpenCageGeocoding.esm.js.map
index 0a3cc5e..1c6fad3 100644
--- a/dist/js/L.Control.OpenCageGeocoding.esm.js.map
+++ b/dist/js/L.Control.OpenCageGeocoding.esm.js.map
@@ -1 +1 @@
-{"version":3,"file":"L.Control.OpenCageGeocoding.esm.js","names":[],"sources":["../../src/js/geocoder.js","../../src/js/L.Control.OpenCageGeocoding.js"],"sourcesContent":["import L from 'leaflet';\n\n/**\n * Geocoder class for OpenCage API interactions\n */\nexport class OpenCageGeocoder {\n constructor(options = {}) {\n this.options = {\n serviceUrl: 'https://api.opencagedata.com/geocode/v1/json/',\n geocodingQueryParams: {},\n reverseQueryParams: {},\n key: '',\n limit: 5,\n ...options,\n };\n }\n\n /**\n * Geocode a query string\n * @param {string} query - The search query\n * @param {Function} callback - Callback function to handle results\n * @param {Object} context - Context object (typically the control instance)\n */\n geocode(query, callback, context) {\n const proximity = this._getProximity(context);\n\n const params = {\n q: query,\n limit: this.options.limit,\n key: this.options.key,\n ...proximity,\n ...this.options.geocodingQueryParams,\n };\n\n this._makeRequest(this.options.serviceUrl, params, (data) => {\n const results = this._processResults(data);\n callback.call(context, results);\n });\n }\n\n /**\n * Reverse geocode a location\n * @param {Object} location - Location object with lat/lng\n * @param {number} scale - Scale parameter (unused in current implementation)\n * @param {Function} callback - Callback function to handle results\n * @param {Object} context - Context object\n */\n reverse(location, scale, callback, context) {\n this.geocode(location, callback, context);\n }\n\n /**\n * Get proximity parameter from map center\n * @private\n */\n _getProximity(context) {\n const proximity = {};\n if (context && context._map && context._map.getCenter()) {\n const center = context._map.getCenter();\n proximity.proximity = center.lat + ',' + center.lng;\n }\n return proximity;\n }\n\n /**\n * Process API results into standardized format\n * @private\n */\n _processResults(data) {\n const results = [];\n\n for (let i = data.results.length - 1; i >= 0; i--) {\n results[i] = {\n name: data.results[i].formatted,\n center: L.latLng(\n data.results[i].geometry.lat,\n data.results[i].geometry.lng\n ),\n };\n\n if (data.results[i].bounds) {\n results[i].bounds = L.latLngBounds(\n [\n data.results[i].bounds.southwest.lat,\n data.results[i].bounds.southwest.lng,\n ],\n [\n data.results[i].bounds.northeast.lat,\n data.results[i].bounds.northeast.lng,\n ]\n );\n }\n\n if (this.options.resultExtension) {\n this._addResultExtensions(results[i], data.results[i]);\n }\n }\n\n return results;\n }\n\n /**\n * Add result extensions based on configuration\n * @private\n */\n _addResultExtensions(result, apiResult) {\n const resultExtObj = this.options.resultExtension;\n const resultExtKeys = Object.keys(resultExtObj);\n\n for (let j = resultExtKeys.length - 1; j >= 0; j--) {\n const key = resultExtKeys[j];\n let resultAttr = apiResult;\n\n const attrPathKeys = resultExtObj[key].split('.');\n for (let k = 0; k < attrPathKeys.length; k++) {\n const keypath = attrPathKeys[k];\n if (resultAttr[keypath]) {\n resultAttr = resultAttr[keypath];\n } else {\n resultAttr = undefined;\n break;\n }\n }\n result[key] = resultAttr;\n }\n }\n\n _makeRequest(url, params, callback) {\n const queryString = new URLSearchParams(params).toString();\n fetch(`${url}?${queryString}`)\n .then((response) => (response.ok ? response.json() : { results: [] }))\n .then((data) => callback(data))\n .catch(() => callback({ results: [] }));\n }\n}\n\n/**\n * Factory function for creating geocoder instances\n */\nexport function createGeocoder(options) {\n return new OpenCageGeocoder(options);\n}\n","import L from 'leaflet';\nimport { OpenCageGeocoder } from './geocoder.js';\nimport '../css/L.Control.OpenCageGeocoding.css';\n\n/**\n * OpenCage Geocoding Control for Leaflet\n */\nexport class OpenCageGeocodingControl extends L.Control {\n constructor(options = {}) {\n super(options);\n this.options = {\n showResultIcons: false,\n collapsed: true,\n expand: 'click',\n position: 'topright',\n placeholder: 'Search...',\n errorMessage: 'Nothing found.',\n key: '',\n onResultClick: undefined,\n addResultToMap: true,\n limit: 5,\n ...options,\n };\n\n this._callbackId = 0;\n\n if (!this.options.geocoder) {\n this.options.geocoder = new OpenCageGeocoder(this.options);\n }\n }\n onAdd(map) {\n const className = 'leaflet-control-opencage-geocoding';\n const container = L.DomUtil.create('div', className);\n const icon = L.DomUtil.create(\n 'div',\n 'leaflet-control-opencage-geocoding-icon',\n container\n );\n const form = L.DomUtil.create('form', className + '-form', container);\n this._form = form;\n\n this._map = map;\n this._container = container;\n\n const input = L.DomUtil.create('input');\n this._input = input;\n input.type = 'text';\n input.placeholder = this.options.placeholder;\n\n L.DomEvent.addListener(input, 'keydown', this._keydown, this);\n\n this._errorElement = document.createElement('div');\n this._errorElement.className = className + '-form-no-error';\n this._errorElement.textContent = this.options.errorMessage;\n\n this._alts = L.DomUtil.create(\n 'ul',\n className +\n '-alternatives leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n\n form.appendChild(input);\n form.appendChild(this._errorElement);\n container.appendChild(this._alts);\n\n L.DomEvent.addListener(form, 'submit', this._geocode, this);\n\n if (this.options.collapsed) {\n if (this.options.expand === 'click') {\n L.DomEvent.addListener(\n icon,\n 'click',\n (e) => {\n if (e.button === 0 && e.detail !== 2) {\n this._toggle();\n }\n },\n this\n );\n } else {\n L.DomEvent.addListener(icon, 'mouseover', this._expand, this);\n L.DomEvent.addListener(icon, 'mouseout', this._collapse, this);\n this._map.on('movestart', this._collapse, this);\n }\n } else {\n this._expand();\n }\n\n L.DomEvent.disableClickPropagation(container);\n\n return container;\n }\n\n /**\n * Handle geocoding results\n */\n _geocodeResult(results) {\n L.DomUtil.removeClass(\n this._container,\n 'leaflet-control-opencage-geocoding-spinner'\n );\n\n if (results.length === 1) {\n this._geocodeResultSelected(results[0]);\n } else if (results.length > 0) {\n this._alts.innerText = '';\n this._results = results;\n L.DomUtil.removeClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n for (let i = 0; i < results.length; i++) {\n this._alts.appendChild(this._createAlt(results[i], i));\n }\n } else {\n L.DomUtil.addClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n }\n\n /**\n * Mark geocode result on map\n */\n markGeocode(result) {\n if (result.bounds) {\n // console.log(`fit bounds`, result.bounds);\n this._map.fitBounds(result.bounds);\n } else {\n // console.log(`pan to`, result.center);\n this._map.panTo(result.center);\n }\n\n if (this._geocodeMarker) {\n this._map.removeLayer(this._geocodeMarker);\n }\n\n // .bindPopup treats the input as HTML\n const popupEl = document.createElement('div');\n popupEl.textContent = result.name;\n\n this._geocodeMarker = new L.Marker(result.center)\n .bindPopup(popupEl)\n .addTo(this._map)\n .openPopup();\n\n return this;\n }\n\n /**\n * Perform geocoding\n */\n _geocode(event) {\n L.DomEvent.preventDefault(event);\n\n L.DomUtil.addClass(\n this._container,\n 'leaflet-control-opencage-geocoding-spinner'\n );\n this._clearResults();\n this.options.geocoder.geocode(this._input.value, this._geocodeResult, this);\n\n return false;\n }\n\n /**\n * Handle selected geocoding result\n */\n _geocodeResultSelected(result) {\n if (this.options.collapsed) {\n this._collapse();\n } else {\n this._clearResults();\n }\n\n if (\n this.options.onResultClick &&\n typeof this.options.onResultClick === 'function'\n ) {\n this.options.onResultClick(result);\n }\n\n if (this.options.addResultToMap) {\n this.markGeocode(result);\n }\n }\n\n /**\n * Toggle control expansion\n */\n _toggle() {\n if (\n this._container.className.indexOf(\n 'leaflet-control-opencage-geocoding-expanded'\n ) >= 0\n ) {\n this._collapse();\n } else {\n this._expand();\n }\n }\n\n /**\n * Expand the control\n */\n _expand() {\n L.DomUtil.addClass(\n this._container,\n 'leaflet-control-opencage-geocoding-expanded'\n );\n this._input.select();\n }\n\n /**\n * Collapse the control\n */\n _collapse() {\n this._container.className = this._container.className.replace(\n ' leaflet-control-opencage-geocoding-expanded',\n ''\n );\n L.DomUtil.addClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n L.DomUtil.removeClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n\n /**\n * Clear results display\n */\n _clearResults() {\n L.DomUtil.addClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n this._selection = null;\n L.DomUtil.removeClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n\n /**\n * Create alternative result element\n */\n _createAlt(result, index) {\n const li = document.createElement('li');\n const a = document.createElement('a');\n a.href = '#';\n a.dataset.resultIndex = index;\n\n if (this.options.showResultIcons && result.icon) {\n try {\n const u = new URL(result.icon, window.location.href);\n if (u.protocol === 'http:' || u.protocol === 'https:') {\n const img = document.createElement('img');\n img.src = u.href;\n img.alt = '';\n a.appendChild(img);\n }\n } catch {\n // ignore invalid URL\n }\n }\n\n a.appendChild(document.createTextNode(result.name));\n li.appendChild(a);\n\n L.DomEvent.addListener(\n li,\n 'click',\n () => {\n this._geocodeResultSelected(result);\n },\n this\n );\n\n return li;\n }\n\n /**\n * Handle keyboard navigation\n */\n _keydown(e) {\n const select = (dir) => {\n if (this._selection) {\n L.DomUtil.removeClass(\n this._selection.firstChild,\n 'leaflet-control-opencage-geocoding-selected'\n );\n this._selection =\n this._selection[dir > 0 ? 'nextSibling' : 'previousSibling'];\n }\n\n if (!this._selection) {\n this._selection = this._alts[dir > 0 ? 'firstChild' : 'lastChild'];\n }\n\n if (this._selection) {\n L.DomUtil.addClass(\n this._selection.firstChild,\n 'leaflet-control-opencage-geocoding-selected'\n );\n }\n };\n\n switch (e.keyCode) {\n case 38: // Up\n select(-1);\n L.DomEvent.preventDefault(e);\n break;\n case 40: // Down\n select(1);\n L.DomEvent.preventDefault(e);\n break;\n case 13: // Enter\n if (this._selection) {\n const index = parseInt(\n this._selection.firstChild.getAttribute('data-result-index'),\n 10\n );\n this._geocodeResultSelected(this._results[index]);\n this._clearResults();\n L.DomEvent.preventDefault(e);\n }\n break;\n }\n return true;\n }\n}\n\n// Factory function for backwards compatibility\nconst openCageGeocoding = (options) => {\n return new OpenCageGeocodingControl(options);\n};\n\n// Attach geocoder to the control class\nOpenCageGeocodingControl.Geocoder = OpenCageGeocoder;\nOpenCageGeocodingControl.geocoder = (options) => {\n return new OpenCageGeocoder(options);\n};\n\n// Attach to L.Control for global usage\nObject.assign(L.Control, {\n OpenCageGeocoding: OpenCageGeocodingControl,\n openCageGeocoding: openCageGeocoding,\n});\n\n// Export for ES modules\nexport { OpenCageGeocoder, openCageGeocoding };\nexport default OpenCageGeocodingControl;\n"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"L.Control.OpenCageGeocoding.esm.js","names":[],"sources":["../../src/js/geocoder.js","../../src/js/L.Control.OpenCageGeocoding.js"],"sourcesContent":["import L from 'leaflet';\n\n/**\n * Geocoder class for OpenCage API interactions\n */\nexport class OpenCageGeocoder {\n constructor(options = {}) {\n this.options = {\n serviceUrl: 'https://api.opencagedata.com/geocode/v1/json/',\n geocodingQueryParams: {},\n reverseQueryParams: {},\n key: '',\n limit: 5,\n ...options,\n };\n }\n\n /**\n * Geocode a query string\n * @param {string} query - The search query\n * @param {Function} callback - Callback function to handle results\n * @param {Object} context - Context object (typically the control instance)\n */\n geocode(query, callback, context) {\n const proximity = this._getProximity(context);\n\n const params = {\n q: query,\n limit: this.options.limit,\n key: this.options.key,\n ...proximity,\n ...this.options.geocodingQueryParams,\n };\n\n this._request(params, callback, context);\n }\n\n /**\n * Reverse geocode a location\n *\n * Use `reverseQueryParams` to add parameters here. `limit` is omitted\n * because the API returns at most one reverse geocoding result.\n *\n * @param {L.LatLng|Array|Object} location - Anything L.latLng() accepts:\n * an L.LatLng, [lat, lng] or {lat, lng}\n * @param {number} scale - Ignored\n * @param {Function} callback - Callback function to handle results\n * @param {Object} context - Context object\n */\n reverse(location, scale, callback, context) {\n const latLng = L.latLng(location);\n\n if (!latLng) {\n throw new TypeError(\n `Invalid location for reverse geocoding: ${JSON.stringify(location)}. ` +\n 'Expected an L.LatLng, [lat, lng] or {lat, lng}'\n );\n }\n\n const params = {\n q: `${latLng.lat},${latLng.lng}`,\n key: this.options.key,\n ...this.options.reverseQueryParams,\n };\n\n this._request(params, callback, context);\n }\n\n /**\n * Send a request and deliver the processed results to the callback\n * @private\n */\n _request(params, callback, context) {\n this._makeRequest(this.options.serviceUrl, params, (data) => {\n const results = this._processResults(data);\n callback.call(context, results);\n });\n }\n\n /**\n * Get proximity parameter from map center\n * @private\n */\n _getProximity(context) {\n const proximity = {};\n if (context && context._map && context._map.getCenter()) {\n const center = context._map.getCenter();\n proximity.proximity = center.lat + ',' + center.lng;\n }\n return proximity;\n }\n\n /**\n * Process API results into standardized format\n * @private\n */\n _processResults(data) {\n const results = [];\n\n for (let i = data.results.length - 1; i >= 0; i--) {\n results[i] = {\n name: data.results[i].formatted,\n center: L.latLng(\n data.results[i].geometry.lat,\n data.results[i].geometry.lng\n ),\n };\n\n if (data.results[i].bounds) {\n results[i].bounds = L.latLngBounds(\n [\n data.results[i].bounds.southwest.lat,\n data.results[i].bounds.southwest.lng,\n ],\n [\n data.results[i].bounds.northeast.lat,\n data.results[i].bounds.northeast.lng,\n ]\n );\n }\n\n if (this.options.resultExtension) {\n this._addResultExtensions(results[i], data.results[i]);\n }\n }\n\n return results;\n }\n\n /**\n * Add result extensions based on configuration\n * @private\n */\n _addResultExtensions(result, apiResult) {\n const resultExtObj = this.options.resultExtension;\n const resultExtKeys = Object.keys(resultExtObj);\n\n for (let j = resultExtKeys.length - 1; j >= 0; j--) {\n const key = resultExtKeys[j];\n let resultAttr = apiResult;\n\n const attrPathKeys = resultExtObj[key].split('.');\n for (let k = 0; k < attrPathKeys.length; k++) {\n const keypath = attrPathKeys[k];\n if (resultAttr[keypath]) {\n resultAttr = resultAttr[keypath];\n } else {\n resultAttr = undefined;\n break;\n }\n }\n result[key] = resultAttr;\n }\n }\n\n _makeRequest(url, params, callback) {\n const queryString = new URLSearchParams(params).toString();\n fetch(`${url}?${queryString}`)\n .then((response) => (response.ok ? response.json() : { results: [] }))\n .then((data) => callback(data))\n .catch(() => callback({ results: [] }));\n }\n}\n\n/**\n * Factory function for creating geocoder instances\n */\nexport function createGeocoder(options) {\n return new OpenCageGeocoder(options);\n}\n","import L from 'leaflet';\nimport { OpenCageGeocoder } from './geocoder.js';\nimport '../css/L.Control.OpenCageGeocoding.css';\n\n/**\n * OpenCage Geocoding Control for Leaflet\n */\nexport class OpenCageGeocodingControl extends L.Control {\n constructor(options = {}) {\n super(options);\n this.options = {\n showResultIcons: false,\n collapsed: true,\n expand: 'click',\n position: 'topright',\n placeholder: 'Search...',\n errorMessage: 'Nothing found.',\n key: '',\n onResultClick: undefined,\n addResultToMap: true,\n limit: 5,\n ...options,\n };\n\n this._callbackId = 0;\n\n if (!this.options.geocoder) {\n this.options.geocoder = new OpenCageGeocoder(this.options);\n }\n }\n onAdd(map) {\n const className = 'leaflet-control-opencage-geocoding';\n const container = L.DomUtil.create('div', className);\n const icon = L.DomUtil.create(\n 'div',\n 'leaflet-control-opencage-geocoding-icon',\n container\n );\n const form = L.DomUtil.create('form', className + '-form', container);\n this._form = form;\n\n this._map = map;\n this._container = container;\n\n const input = L.DomUtil.create('input');\n this._input = input;\n input.type = 'text';\n input.placeholder = this.options.placeholder;\n\n L.DomEvent.addListener(input, 'keydown', this._keydown, this);\n\n this._errorElement = document.createElement('div');\n this._errorElement.className = className + '-form-no-error';\n this._errorElement.textContent = this.options.errorMessage;\n\n this._alts = L.DomUtil.create(\n 'ul',\n className +\n '-alternatives leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n\n form.appendChild(input);\n form.appendChild(this._errorElement);\n container.appendChild(this._alts);\n\n L.DomEvent.addListener(form, 'submit', this._geocode, this);\n\n if (this.options.collapsed) {\n if (this.options.expand === 'click') {\n L.DomEvent.addListener(\n icon,\n 'click',\n (e) => {\n if (e.button === 0 && e.detail !== 2) {\n this._toggle();\n }\n },\n this\n );\n } else {\n L.DomEvent.addListener(icon, 'mouseover', this._expand, this);\n L.DomEvent.addListener(icon, 'mouseout', this._collapse, this);\n this._map.on('movestart', this._collapse, this);\n }\n } else {\n this._expand();\n }\n\n L.DomEvent.disableClickPropagation(container);\n\n return container;\n }\n\n /**\n * Handle geocoding results\n */\n _geocodeResult(results) {\n L.DomUtil.removeClass(\n this._container,\n 'leaflet-control-opencage-geocoding-spinner'\n );\n\n if (results.length === 1) {\n this._geocodeResultSelected(results[0]);\n } else if (results.length > 0) {\n this._alts.innerText = '';\n this._results = results;\n L.DomUtil.removeClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n for (let i = 0; i < results.length; i++) {\n this._alts.appendChild(this._createAlt(results[i], i));\n }\n } else {\n L.DomUtil.addClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n }\n\n /**\n * Mark geocode result on map\n */\n markGeocode(result) {\n if (result.bounds) {\n // console.log(`fit bounds`, result.bounds);\n this._map.fitBounds(result.bounds);\n } else {\n // console.log(`pan to`, result.center);\n this._map.panTo(result.center);\n }\n\n if (this._geocodeMarker) {\n this._map.removeLayer(this._geocodeMarker);\n }\n\n // .bindPopup treats the input as HTML\n const popupEl = document.createElement('div');\n popupEl.textContent = result.name;\n\n this._geocodeMarker = new L.Marker(result.center)\n .bindPopup(popupEl)\n .addTo(this._map)\n .openPopup();\n\n return this;\n }\n\n /**\n * Perform geocoding\n */\n _geocode(event) {\n L.DomEvent.preventDefault(event);\n\n L.DomUtil.addClass(\n this._container,\n 'leaflet-control-opencage-geocoding-spinner'\n );\n this._clearResults();\n this.options.geocoder.geocode(this._input.value, this._geocodeResult, this);\n\n return false;\n }\n\n /**\n * Handle selected geocoding result\n */\n _geocodeResultSelected(result) {\n if (this.options.collapsed) {\n this._collapse();\n } else {\n this._clearResults();\n }\n\n if (\n this.options.onResultClick &&\n typeof this.options.onResultClick === 'function'\n ) {\n this.options.onResultClick(result);\n }\n\n if (this.options.addResultToMap) {\n this.markGeocode(result);\n }\n }\n\n /**\n * Toggle control expansion\n */\n _toggle() {\n if (\n this._container.className.indexOf(\n 'leaflet-control-opencage-geocoding-expanded'\n ) >= 0\n ) {\n this._collapse();\n } else {\n this._expand();\n }\n }\n\n /**\n * Expand the control\n */\n _expand() {\n L.DomUtil.addClass(\n this._container,\n 'leaflet-control-opencage-geocoding-expanded'\n );\n this._input.select();\n }\n\n /**\n * Collapse the control\n */\n _collapse() {\n this._container.className = this._container.className.replace(\n ' leaflet-control-opencage-geocoding-expanded',\n ''\n );\n L.DomUtil.addClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n L.DomUtil.removeClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n\n /**\n * Clear results display\n */\n _clearResults() {\n L.DomUtil.addClass(\n this._alts,\n 'leaflet-control-opencage-geocoding-alternatives-minimized'\n );\n this._selection = null;\n L.DomUtil.removeClass(\n this._errorElement,\n 'leaflet-control-opencage-geocoding-error'\n );\n }\n\n /**\n * Create alternative result element\n */\n _createAlt(result, index) {\n const li = document.createElement('li');\n const a = document.createElement('a');\n a.href = '#';\n a.dataset.resultIndex = index;\n\n if (this.options.showResultIcons && result.icon) {\n try {\n const u = new URL(result.icon, window.location.href);\n if (u.protocol === 'http:' || u.protocol === 'https:') {\n const img = document.createElement('img');\n img.src = u.href;\n img.alt = '';\n a.appendChild(img);\n }\n } catch {\n // ignore invalid URL\n }\n }\n\n a.appendChild(document.createTextNode(result.name));\n li.appendChild(a);\n\n L.DomEvent.addListener(\n li,\n 'click',\n () => {\n this._geocodeResultSelected(result);\n },\n this\n );\n\n return li;\n }\n\n /**\n * Handle keyboard navigation\n */\n _keydown(e) {\n const select = (dir) => {\n if (this._selection) {\n L.DomUtil.removeClass(\n this._selection.firstChild,\n 'leaflet-control-opencage-geocoding-selected'\n );\n this._selection =\n this._selection[dir > 0 ? 'nextSibling' : 'previousSibling'];\n }\n\n if (!this._selection) {\n this._selection = this._alts[dir > 0 ? 'firstChild' : 'lastChild'];\n }\n\n if (this._selection) {\n L.DomUtil.addClass(\n this._selection.firstChild,\n 'leaflet-control-opencage-geocoding-selected'\n );\n }\n };\n\n switch (e.keyCode) {\n case 38: // Up\n select(-1);\n L.DomEvent.preventDefault(e);\n break;\n case 40: // Down\n select(1);\n L.DomEvent.preventDefault(e);\n break;\n case 13: // Enter\n if (this._selection) {\n const index = parseInt(\n this._selection.firstChild.getAttribute('data-result-index'),\n 10\n );\n this._geocodeResultSelected(this._results[index]);\n this._clearResults();\n L.DomEvent.preventDefault(e);\n }\n break;\n }\n return true;\n }\n}\n\n// Factory function for backwards compatibility\nconst openCageGeocoding = (options) => {\n return new OpenCageGeocodingControl(options);\n};\n\n// Attach geocoder to the control class\nOpenCageGeocodingControl.Geocoder = OpenCageGeocoder;\nOpenCageGeocodingControl.geocoder = (options) => {\n return new OpenCageGeocoder(options);\n};\n\n// Attach to L.Control for global usage\nObject.assign(L.Control, {\n OpenCageGeocoding: OpenCageGeocodingControl,\n openCageGeocoding: openCageGeocoding,\n});\n\n// Export for ES modules\nexport { OpenCageGeocoder, openCageGeocoding };\nexport default OpenCageGeocodingControl;\n"],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/js/L.Control.OpenCageGeocoding.min.js b/dist/js/L.Control.OpenCageGeocoding.min.js
index c423be0..7363ab9 100644
--- a/dist/js/L.Control.OpenCageGeocoding.min.js
+++ b/dist/js/L.Control.OpenCageGeocoding.min.js
@@ -1,5 +1,5 @@
/**
- * OpenCage Data Geocoding Control v2.4.2 - 2026-08-05
+ * OpenCage Data Geocoding Control v2.5.0 - 2026-08-27
* Copyright (c) 2026, OpenCage GmbH
* support@opencagedata.com
* https://opencagedata.com
@@ -8,4 +8,4 @@
* Demo: https://opencagedata.com
* Source: git@github.com:opencagedata/leaflet-opencage-geocoding.git
*/
-(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("leaflet")):typeof define==`function`&&define.amd?define([`exports`,`leaflet`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e[`leaflet-control-opencage-geocoding`]={},e.L))})(this,function(e,t){Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var n=Object.create,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,o=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(e,t,n,o)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=a(t),l=0,u=c.length,d;lt[e]).bind(null,d),enumerable:!(o=i(t,d))||o.enumerable});return e};t=((e,t,i)=>(i=e==null?{}:n(o(e)),c(t||!e||!e.__esModule||!s.call(e,`default`)?r(i,`default`,{value:e,enumerable:!0}):i,e)))(t,1);var l=class{constructor(e={}){this.options={serviceUrl:`https://api.opencagedata.com/geocode/v1/json/`,geocodingQueryParams:{},reverseQueryParams:{},key:``,limit:5,...e}}geocode(e,t,n){let r=this._getProximity(n),i={q:e,limit:this.options.limit,key:this.options.key,...r,...this.options.geocodingQueryParams};this._makeRequest(this.options.serviceUrl,i,e=>{let r=this._processResults(e);t.call(n,r)})}reverse(e,t,n,r){this.geocode(e,n,r)}_getProximity(e){let t={};if(e&&e._map&&e._map.getCenter()){let n=e._map.getCenter();t.proximity=n.lat+`,`+n.lng}return t}_processResults(e){let n=[];for(let r=e.results.length-1;r>=0;r--)n[r]={name:e.results[r].formatted,center:t.default.latLng(e.results[r].geometry.lat,e.results[r].geometry.lng)},e.results[r].bounds&&(n[r].bounds=t.default.latLngBounds([e.results[r].bounds.southwest.lat,e.results[r].bounds.southwest.lng],[e.results[r].bounds.northeast.lat,e.results[r].bounds.northeast.lng])),this.options.resultExtension&&this._addResultExtensions(n[r],e.results[r]);return n}_addResultExtensions(e,t){let n=this.options.resultExtension,r=Object.keys(n);for(let i=r.length-1;i>=0;i--){let a=r[i],o=t,s=n[a].split(`.`);for(let e=0;ee.ok?e.json():{results:[]}).then(e=>n(e)).catch(()=>n({results:[]}))}},u=class extends t.default.Control{constructor(e={}){super(e),this.options={showResultIcons:!1,collapsed:!0,expand:`click`,position:`topright`,placeholder:`Search...`,errorMessage:`Nothing found.`,key:``,onResultClick:void 0,addResultToMap:!0,limit:5,...e},this._callbackId=0,this.options.geocoder||(this.options.geocoder=new l(this.options))}onAdd(e){let n=t.default.DomUtil.create(`div`,`leaflet-control-opencage-geocoding`),r=t.default.DomUtil.create(`div`,`leaflet-control-opencage-geocoding-icon`,n),i=t.default.DomUtil.create(`form`,`leaflet-control-opencage-geocoding-form`,n);this._form=i,this._map=e,this._container=n;let a=t.default.DomUtil.create(`input`);return this._input=a,a.type=`text`,a.placeholder=this.options.placeholder,t.default.DomEvent.addListener(a,`keydown`,this._keydown,this),this._errorElement=document.createElement(`div`),this._errorElement.className=`leaflet-control-opencage-geocoding-form-no-error`,this._errorElement.textContent=this.options.errorMessage,this._alts=t.default.DomUtil.create(`ul`,`leaflet-control-opencage-geocoding-alternatives leaflet-control-opencage-geocoding-alternatives-minimized`),i.appendChild(a),i.appendChild(this._errorElement),n.appendChild(this._alts),t.default.DomEvent.addListener(i,`submit`,this._geocode,this),this.options.collapsed?this.options.expand===`click`?t.default.DomEvent.addListener(r,`click`,e=>{e.button===0&&e.detail!==2&&this._toggle()},this):(t.default.DomEvent.addListener(r,`mouseover`,this._expand,this),t.default.DomEvent.addListener(r,`mouseout`,this._collapse,this),this._map.on(`movestart`,this._collapse,this)):this._expand(),t.default.DomEvent.disableClickPropagation(n),n}_geocodeResult(e){if(t.default.DomUtil.removeClass(this._container,`leaflet-control-opencage-geocoding-spinner`),e.length===1)this._geocodeResultSelected(e[0]);else if(e.length>0){this._alts.innerText=``,this._results=e,t.default.DomUtil.removeClass(this._alts,`leaflet-control-opencage-geocoding-alternatives-minimized`);for(let t=0;t=0?this._collapse():this._expand()}_expand(){t.default.DomUtil.addClass(this._container,`leaflet-control-opencage-geocoding-expanded`),this._input.select()}_collapse(){this._container.className=this._container.className.replace(` leaflet-control-opencage-geocoding-expanded`,``),t.default.DomUtil.addClass(this._alts,`leaflet-control-opencage-geocoding-alternatives-minimized`),t.default.DomUtil.removeClass(this._errorElement,`leaflet-control-opencage-geocoding-error`)}_clearResults(){t.default.DomUtil.addClass(this._alts,`leaflet-control-opencage-geocoding-alternatives-minimized`),this._selection=null,t.default.DomUtil.removeClass(this._errorElement,`leaflet-control-opencage-geocoding-error`)}_createAlt(e,n){let r=document.createElement(`li`),i=document.createElement(`a`);if(i.href=`#`,i.dataset.resultIndex=n,this.options.showResultIcons&&e.icon)try{let t=new URL(e.icon,window.location.href);if(t.protocol===`http:`||t.protocol===`https:`){let e=document.createElement(`img`);e.src=t.href,e.alt=``,i.appendChild(e)}}catch{}return i.appendChild(document.createTextNode(e.name)),r.appendChild(i),t.default.DomEvent.addListener(r,`click`,()=>{this._geocodeResultSelected(e)},this),r}_keydown(e){let n=e=>{this._selection&&=(t.default.DomUtil.removeClass(this._selection.firstChild,`leaflet-control-opencage-geocoding-selected`),this._selection[e>0?`nextSibling`:`previousSibling`]),this._selection||=this._alts[e>0?`firstChild`:`lastChild`],this._selection&&t.default.DomUtil.addClass(this._selection.firstChild,`leaflet-control-opencage-geocoding-selected`)};switch(e.keyCode){case 38:n(-1),t.default.DomEvent.preventDefault(e);break;case 40:n(1),t.default.DomEvent.preventDefault(e);break;case 13:if(this._selection){let n=parseInt(this._selection.firstChild.getAttribute(`data-result-index`),10);this._geocodeResultSelected(this._results[n]),this._clearResults(),t.default.DomEvent.preventDefault(e)}}return!0}},d=e=>new u(e);u.Geocoder=l,u.geocoder=e=>new l(e),Object.assign(t.default.Control,{OpenCageGeocoding:u,openCageGeocoding:d}),e.OpenCageGeocoder=l,e.OpenCageGeocodingControl=u,e.default=u,e.openCageGeocoding=d});
\ No newline at end of file
+(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("leaflet")):typeof define==`function`&&define.amd?define([`exports`,`leaflet`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e[`leaflet-control-opencage-geocoding`]={},e.L))})(this,function(e,t){Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var n=Object.create,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,o=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(e,t,n,o)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=a(t),l=0,u=c.length,d;lt[e]).bind(null,d),enumerable:!(o=i(t,d))||o.enumerable});return e};t=((e,t,i)=>(i=e==null?{}:n(o(e)),c(t||!e||!e.__esModule||!s.call(e,`default`)?r(i,`default`,{value:e,enumerable:!0}):i,e)))(t,1);var l=class{constructor(e={}){this.options={serviceUrl:`https://api.opencagedata.com/geocode/v1/json/`,geocodingQueryParams:{},reverseQueryParams:{},key:``,limit:5,...e}}geocode(e,t,n){let r=this._getProximity(n),i={q:e,limit:this.options.limit,key:this.options.key,...r,...this.options.geocodingQueryParams};this._request(i,t,n)}reverse(e,n,r,i){let a=t.default.latLng(e);if(!a)throw TypeError(`Invalid location for reverse geocoding: ${JSON.stringify(e)}. Expected an L.LatLng, [lat, lng] or {lat, lng}`);let o={q:`${a.lat},${a.lng}`,key:this.options.key,...this.options.reverseQueryParams};this._request(o,r,i)}_request(e,t,n){this._makeRequest(this.options.serviceUrl,e,e=>{let r=this._processResults(e);t.call(n,r)})}_getProximity(e){let t={};if(e&&e._map&&e._map.getCenter()){let n=e._map.getCenter();t.proximity=n.lat+`,`+n.lng}return t}_processResults(e){let n=[];for(let r=e.results.length-1;r>=0;r--)n[r]={name:e.results[r].formatted,center:t.default.latLng(e.results[r].geometry.lat,e.results[r].geometry.lng)},e.results[r].bounds&&(n[r].bounds=t.default.latLngBounds([e.results[r].bounds.southwest.lat,e.results[r].bounds.southwest.lng],[e.results[r].bounds.northeast.lat,e.results[r].bounds.northeast.lng])),this.options.resultExtension&&this._addResultExtensions(n[r],e.results[r]);return n}_addResultExtensions(e,t){let n=this.options.resultExtension,r=Object.keys(n);for(let i=r.length-1;i>=0;i--){let a=r[i],o=t,s=n[a].split(`.`);for(let e=0;ee.ok?e.json():{results:[]}).then(e=>n(e)).catch(()=>n({results:[]}))}},u=class extends t.default.Control{constructor(e={}){super(e),this.options={showResultIcons:!1,collapsed:!0,expand:`click`,position:`topright`,placeholder:`Search...`,errorMessage:`Nothing found.`,key:``,onResultClick:void 0,addResultToMap:!0,limit:5,...e},this._callbackId=0,this.options.geocoder||(this.options.geocoder=new l(this.options))}onAdd(e){let n=t.default.DomUtil.create(`div`,`leaflet-control-opencage-geocoding`),r=t.default.DomUtil.create(`div`,`leaflet-control-opencage-geocoding-icon`,n),i=t.default.DomUtil.create(`form`,`leaflet-control-opencage-geocoding-form`,n);this._form=i,this._map=e,this._container=n;let a=t.default.DomUtil.create(`input`);return this._input=a,a.type=`text`,a.placeholder=this.options.placeholder,t.default.DomEvent.addListener(a,`keydown`,this._keydown,this),this._errorElement=document.createElement(`div`),this._errorElement.className=`leaflet-control-opencage-geocoding-form-no-error`,this._errorElement.textContent=this.options.errorMessage,this._alts=t.default.DomUtil.create(`ul`,`leaflet-control-opencage-geocoding-alternatives leaflet-control-opencage-geocoding-alternatives-minimized`),i.appendChild(a),i.appendChild(this._errorElement),n.appendChild(this._alts),t.default.DomEvent.addListener(i,`submit`,this._geocode,this),this.options.collapsed?this.options.expand===`click`?t.default.DomEvent.addListener(r,`click`,e=>{e.button===0&&e.detail!==2&&this._toggle()},this):(t.default.DomEvent.addListener(r,`mouseover`,this._expand,this),t.default.DomEvent.addListener(r,`mouseout`,this._collapse,this),this._map.on(`movestart`,this._collapse,this)):this._expand(),t.default.DomEvent.disableClickPropagation(n),n}_geocodeResult(e){if(t.default.DomUtil.removeClass(this._container,`leaflet-control-opencage-geocoding-spinner`),e.length===1)this._geocodeResultSelected(e[0]);else if(e.length>0){this._alts.innerText=``,this._results=e,t.default.DomUtil.removeClass(this._alts,`leaflet-control-opencage-geocoding-alternatives-minimized`);for(let t=0;t=0?this._collapse():this._expand()}_expand(){t.default.DomUtil.addClass(this._container,`leaflet-control-opencage-geocoding-expanded`),this._input.select()}_collapse(){this._container.className=this._container.className.replace(` leaflet-control-opencage-geocoding-expanded`,``),t.default.DomUtil.addClass(this._alts,`leaflet-control-opencage-geocoding-alternatives-minimized`),t.default.DomUtil.removeClass(this._errorElement,`leaflet-control-opencage-geocoding-error`)}_clearResults(){t.default.DomUtil.addClass(this._alts,`leaflet-control-opencage-geocoding-alternatives-minimized`),this._selection=null,t.default.DomUtil.removeClass(this._errorElement,`leaflet-control-opencage-geocoding-error`)}_createAlt(e,n){let r=document.createElement(`li`),i=document.createElement(`a`);if(i.href=`#`,i.dataset.resultIndex=n,this.options.showResultIcons&&e.icon)try{let t=new URL(e.icon,window.location.href);if(t.protocol===`http:`||t.protocol===`https:`){let e=document.createElement(`img`);e.src=t.href,e.alt=``,i.appendChild(e)}}catch{}return i.appendChild(document.createTextNode(e.name)),r.appendChild(i),t.default.DomEvent.addListener(r,`click`,()=>{this._geocodeResultSelected(e)},this),r}_keydown(e){let n=e=>{this._selection&&=(t.default.DomUtil.removeClass(this._selection.firstChild,`leaflet-control-opencage-geocoding-selected`),this._selection[e>0?`nextSibling`:`previousSibling`]),this._selection||=this._alts[e>0?`firstChild`:`lastChild`],this._selection&&t.default.DomUtil.addClass(this._selection.firstChild,`leaflet-control-opencage-geocoding-selected`)};switch(e.keyCode){case 38:n(-1),t.default.DomEvent.preventDefault(e);break;case 40:n(1),t.default.DomEvent.preventDefault(e);break;case 13:if(this._selection){let n=parseInt(this._selection.firstChild.getAttribute(`data-result-index`),10);this._geocodeResultSelected(this._results[n]),this._clearResults(),t.default.DomEvent.preventDefault(e)}}return!0}},d=e=>new u(e);u.Geocoder=l,u.geocoder=e=>new l(e),Object.assign(t.default.Control,{OpenCageGeocoding:u,openCageGeocoding:d}),e.OpenCageGeocoder=l,e.OpenCageGeocodingControl=u,e.default=u,e.openCageGeocoding=d});
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 48c11e2..8c7c036 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@opencage/leaflet-opencage-geocoding",
- "version": "2.4.2",
+ "version": "2.5.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@opencage/leaflet-opencage-geocoding",
- "version": "2.4.2",
+ "version": "2.5.0",
"license": "BSD",
"dependencies": {
"leaflet": "^1.9"
diff --git a/package.json b/package.json
index 3cde941..4aaf0d3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencage/leaflet-opencage-geocoding",
- "version": "2.4.2",
+ "version": "2.5.0",
"type": "module",
"description": "Leaflet plugin for geocoding via the OpenCage Geocoder API",
"repository": {
diff --git a/src/js/geocoder.js b/src/js/geocoder.js
index 4dfc5cc..98aad68 100644
--- a/src/js/geocoder.js
+++ b/src/js/geocoder.js
@@ -32,21 +32,49 @@ export class OpenCageGeocoder {
...this.options.geocodingQueryParams,
};
- this._makeRequest(this.options.serviceUrl, params, (data) => {
- const results = this._processResults(data);
- callback.call(context, results);
- });
+ this._request(params, callback, context);
}
/**
* Reverse geocode a location
- * @param {Object} location - Location object with lat/lng
- * @param {number} scale - Scale parameter (unused in current implementation)
+ *
+ * Use `reverseQueryParams` to add parameters here. `limit` is omitted
+ * because the API returns at most one reverse geocoding result.
+ *
+ * @param {L.LatLng|Array|Object} location - Anything L.latLng() accepts:
+ * an L.LatLng, [lat, lng] or {lat, lng}
+ * @param {number} scale - Ignored
* @param {Function} callback - Callback function to handle results
* @param {Object} context - Context object
*/
reverse(location, scale, callback, context) {
- this.geocode(location, callback, context);
+ const latLng = L.latLng(location);
+
+ if (!latLng) {
+ throw new TypeError(
+ `Invalid location for reverse geocoding: ${JSON.stringify(location)}. ` +
+ 'Expected an L.LatLng, [lat, lng] or {lat, lng}'
+ );
+ }
+
+ const params = {
+ q: `${latLng.lat},${latLng.lng}`,
+ key: this.options.key,
+ ...this.options.reverseQueryParams,
+ };
+
+ this._request(params, callback, context);
+ }
+
+ /**
+ * Send a request and deliver the processed results to the callback
+ * @private
+ */
+ _request(params, callback, context) {
+ this._makeRequest(this.options.serviceUrl, params, (data) => {
+ const results = this._processResults(data);
+ callback.call(context, results);
+ });
}
/**
diff --git a/test/geocoder.test.js b/test/geocoder.test.js
index c2225f3..cb610c2 100644
--- a/test/geocoder.test.js
+++ b/test/geocoder.test.js
@@ -1,4 +1,5 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
+import L from 'leaflet';
import { OpenCageGeocoder } from '../src/js/geocoder.js';
describe('OpenCageGeocoder', () => {
@@ -110,6 +111,112 @@ describe('OpenCageGeocoder', () => {
});
});
+ describe('reverse', () => {
+ let mockMakeRequest;
+
+ beforeEach(() => {
+ mockMakeRequest = vi.spyOn(geocoder, '_makeRequest');
+ mockMakeRequest.mockImplementation(() => {});
+ });
+
+ const paramsOfLastRequest = () => mockMakeRequest.mock.calls.at(-1)[1];
+
+ it.each([
+ ['an L.LatLng', L.latLng(51.5, -0.12)],
+ ['a {lat, lng} object', { lat: 51.5, lng: -0.12 }],
+ ['a {lat, lon} object', { lat: 51.5, lon: -0.12 }],
+ ['a [lat, lng] array', [51.5, -0.12]],
+ ['numeric strings', { lat: '51.5', lng: '-0.12' }],
+ ])('should send a "lat,lng" query for %s', (_label, location) => {
+ geocoder.reverse(location, 10, mockCallback, mockContext);
+
+ expect(paramsOfLastRequest()).toMatchObject({
+ q: '51.5,-0.12',
+ key: 'test-api-key',
+ });
+ });
+
+ it('should not send proximity or geocodingQueryParams', () => {
+ geocoder.options.geocodingQueryParams = { countrycode: 'gb' };
+
+ geocoder.reverse(
+ { lat: 51.5, lng: -0.12 },
+ 10,
+ mockCallback,
+ mockContext
+ );
+
+ const params = paramsOfLastRequest();
+ expect(params).not.toHaveProperty('proximity');
+ expect(params).not.toHaveProperty('countrycode');
+ expect(params).not.toHaveProperty('limit');
+ });
+
+ it('should apply reverseQueryParams', () => {
+ geocoder.options.reverseQueryParams = {
+ language: 'de',
+ no_annotations: 1,
+ };
+
+ geocoder.reverse(
+ { lat: 51.5, lng: -0.12 },
+ 10,
+ mockCallback,
+ mockContext
+ );
+
+ expect(paramsOfLastRequest()).toMatchObject({
+ q: '51.5,-0.12',
+ language: 'de',
+ no_annotations: 1,
+ });
+ });
+
+ it('should deliver processed results to the callback in context', () => {
+ mockMakeRequest.mockImplementation((url, params, cb) =>
+ cb({
+ results: [
+ { formatted: 'Test Location', geometry: { lat: 51.5, lng: -0.1 } },
+ ],
+ })
+ );
+
+ geocoder.reverse(
+ { lat: 51.5, lng: -0.12 },
+ 10,
+ mockCallback,
+ mockContext
+ );
+
+ expect(mockCallback).toHaveBeenCalledTimes(1);
+ const [results] = mockCallback.mock.calls[0];
+ expect(results).toHaveLength(1);
+ expect(results[0].name).toBe('Test Location');
+ expect(mockCallback.mock.contexts[0]).toBe(mockContext);
+ });
+
+ it.each([
+ ['a string', '51.5,-0.12'],
+ ['an empty object', {}],
+ ['null', null],
+ ['undefined', undefined],
+ ])('should throw a TypeError for %s', (_label, location) => {
+ expect(() =>
+ geocoder.reverse(location, 10, mockCallback, mockContext)
+ ).toThrow(TypeError);
+ expect(mockMakeRequest).not.toHaveBeenCalled();
+ });
+
+ it('should never stringify the location into the query', () => {
+ geocoder.reverse(L.latLng(51.5, -0.12), 10, mockCallback, mockContext);
+
+ const queryString = new URLSearchParams(paramsOfLastRequest()).toString();
+ expect(queryString).toContain('q=51.5%2C-0.12');
+ expect(queryString).not.toContain('LatLng');
+ expect(queryString).not.toContain('object+Object');
+ });
+ });
+
describe('_makeRequest', () => {
it('should fetch the correct URL and call callback with data', async () => {
const mockData = { results: [] };