@maplibre/maplibre-gl-geocoder
Advanced tools
Comparing version 1.6.0 to 1.7.0
@@ -9,4 +9,22 @@ import MaplibreGl, { FlyToOptions, Marker, Popup, MarkerOptions, Map } from 'maplibre-gl'; | ||
id: string; | ||
/** | ||
* Text representing the feature (e.g. "Austin"). | ||
*/ | ||
text: string; | ||
/** | ||
* Optional. The language code of the text returned in text. | ||
*/ | ||
language?: string; | ||
/** | ||
* Human-readable text representing the full result hierarchy (e.g. "Austin, Texas, United States"). | ||
*/ | ||
place_name: string; | ||
text: string; | ||
/** | ||
* An array of index types that this feature may be returned as. Most features have only one type matching its id. | ||
*/ | ||
place_type: string[]; | ||
/** | ||
* Optional. Array bounding box of the form [minx,miny,maxx,maxy]. | ||
*/ | ||
bbox?: [number, number, number, number]; | ||
}; | ||
@@ -163,3 +181,3 @@ type MaplibreGeocoderOptions = { | ||
*/ | ||
externalGeocoder?: (query: string, list: any, confic: MaplibreGeocoderApiConfig) => Promise<CarmenGeojsonFeature[]>; | ||
externalGeocoder?: (query: string, features: CarmenGeojsonFeature[], confic: MaplibreGeocoderApiConfig) => Promise<CarmenGeojsonFeature[]>; | ||
/** | ||
@@ -202,4 +220,18 @@ * A function which accepts a {@link CarmenGeojsonFeature} to filter out results from the Geocoding API response before they are included in the suggestions list. Return `true` to keep the item, `false` otherwise. | ||
*/ | ||
query?: string; | ||
query?: string | number[]; | ||
}; | ||
type MaplibreGeocoderFeatureResults = { | ||
type: "FeatureCollection"; | ||
features: CarmenGeojsonFeature[]; | ||
}; | ||
type MaplibreGeocoderSuggestionResults = { | ||
suggestions: { | ||
text: string; | ||
placeId?: string; | ||
}[]; | ||
}; | ||
type MaplibreGeocoderPlaceResults = { | ||
place: CarmenGeojsonFeature[]; | ||
}; | ||
type MaplibreGeocoderResults = MaplibreGeocoderFeatureResults | MaplibreGeocoderSuggestionResults | MaplibreGeocoderPlaceResults; | ||
/** | ||
@@ -213,20 +245,9 @@ * An API which contains reverseGeocode and forwardGeocode functions to be used by this plugin | ||
*/ | ||
forwardGeocode: (config: MaplibreGeocoderApiConfig) => Promise<{ | ||
features: CarmenGeojsonFeature[]; | ||
}>; | ||
forwardGeocode: (config: MaplibreGeocoderApiConfig) => Promise<MaplibreGeocoderFeatureResults>; | ||
/** | ||
* Reverse geocode function should return an object including a collection of {@link CarmenGeojsonFeature}. | ||
*/ | ||
reverseGeocode: (config: MaplibreGeocoderApiConfig) => Promise<{ | ||
features: CarmenGeojsonFeature[]; | ||
}>; | ||
getSuggestions?: (config: MaplibreGeocoderApiConfig) => Promise<{ | ||
suggestions: { | ||
text: string; | ||
placeId?: string; | ||
}[]; | ||
}>; | ||
searchByPlaceId?: (config: MaplibreGeocoderApiConfig) => Promise<{ | ||
place: CarmenGeojsonFeature[]; | ||
}>; | ||
reverseGeocode: (config: MaplibreGeocoderApiConfig) => Promise<MaplibreGeocoderFeatureResults>; | ||
getSuggestions?: (config: MaplibreGeocoderApiConfig) => Promise<MaplibreGeocoderSuggestionResults>; | ||
searchByPlaceId?: (config: MaplibreGeocoderApiConfig) => Promise<MaplibreGeocoderPlaceResults>; | ||
}; | ||
@@ -286,3 +307,7 @@ /** | ||
_getConfigForRequest(): MaplibreGeocoderApiConfig; | ||
_geocode(searchInput: string, isSuggestion?: boolean, isPlaceId?: boolean): Promise<any>; | ||
_geocode(searchInput: string, isSuggestion?: boolean, isPlaceId?: boolean): Promise<MaplibreGeocoderResults>; | ||
private _createGeocodeRequest; | ||
private _createReverseGeocodeRequest; | ||
private _handleGeocodeResponse; | ||
private _handleGeocodeErrorResponse; | ||
/** | ||
@@ -305,3 +330,3 @@ * Shared logic for clearing input | ||
private _clearOnBlur; | ||
_onQueryResult(response: any): void; | ||
_onQueryResult(results: MaplibreGeocoderResults): void; | ||
_updateProximity(): void; | ||
@@ -314,3 +339,3 @@ _collapse(): void; | ||
*/ | ||
query(searchInput: string): this; | ||
query(searchInput: string): Promise<void>; | ||
_renderError(): void; | ||
@@ -508,2 +533,15 @@ _renderNoResults(): void; | ||
/** | ||
* Subscribe to events that happen within the plugin only once. | ||
* @param type - Event name. | ||
* Available events and the data passed into their respective event objects are: | ||
* | ||
* - __clear__ `Emitted when the input is cleared` | ||
* - __loading__ `{ query } Emitted when the geocoder is looking up a query` | ||
* - __results__ `{ results } Fired when the geocoder returns a response` | ||
* - __result__ `{ result } Fired when input is set` | ||
* - __error__ `{ error } Error as string` | ||
* @returns a Promise that resolves when the event is emitted. | ||
*/ | ||
once(type: string): Promise<any>; | ||
/** | ||
* Remove an event | ||
@@ -513,5 +551,5 @@ * @param type - Event name. | ||
*/ | ||
off(type: string, fn: any): this; | ||
off(type: string, fn: (e: any) => void): this; | ||
} | ||
export { type CarmenGeojsonFeature, type MaplibreGeocoderApi, type MaplibreGeocoderApiConfig, type MaplibreGeocoderOptions, MaplibreGeocoder as default }; | ||
export { type CarmenGeojsonFeature, type MaplibreGeocoderApi, type MaplibreGeocoderApiConfig, type MaplibreGeocoderFeatureResults, type MaplibreGeocoderOptions, type MaplibreGeocoderPlaceResults, type MaplibreGeocoderResults, type MaplibreGeocoderSuggestionResults, MaplibreGeocoder as default }; |
377
lib/index.ts
@@ -12,2 +12,7 @@ import Typeahead from "suggestions-list"; | ||
/** | ||
* A regular expression to match coordinates. | ||
*/ | ||
const COORDINATES_REGEXP = /(-?\d+\.?\d*)[, ]+(-?\d+\.?\d*)[ ]*$/; | ||
/** | ||
* A Carmen GeoJSON Feature. | ||
@@ -18,4 +23,22 @@ * @see https://web.archive.org/web/20210224184722/https://github.com/mapbox/carmen/blob/master/carmen-geojson.md | ||
id: string; | ||
/** | ||
* Text representing the feature (e.g. "Austin"). | ||
*/ | ||
text: string; | ||
/** | ||
* Optional. The language code of the text returned in text. | ||
*/ | ||
language?: string; | ||
/** | ||
* Human-readable text representing the full result hierarchy (e.g. "Austin, Texas, United States"). | ||
*/ | ||
place_name: string; | ||
text: string; | ||
/** | ||
* An array of index types that this feature may be returned as. Most features have only one type matching its id. | ||
*/ | ||
place_type: string[]; | ||
/** | ||
* Optional. Array bounding box of the form [minx,miny,maxx,maxy]. | ||
*/ | ||
bbox?: [number, number, number, number]; | ||
}; | ||
@@ -170,3 +193,3 @@ | ||
*/ | ||
externalGeocoder?: (query: string, list: any, confic: MaplibreGeocoderApiConfig) => Promise<CarmenGeojsonFeature[]>; | ||
externalGeocoder?: (query: string, features: CarmenGeojsonFeature[], confic: MaplibreGeocoderApiConfig) => Promise<CarmenGeojsonFeature[]>; | ||
/** | ||
@@ -210,5 +233,10 @@ * A function which accepts a {@link CarmenGeojsonFeature} to filter out results from the Geocoding API response before they are included in the suggestions list. Return `true` to keep the item, `false` otherwise. | ||
*/ | ||
query?: string; | ||
query?: string | number[]; | ||
} | ||
export type MaplibreGeocoderFeatureResults = { type: "FeatureCollection", features: CarmenGeojsonFeature[]}; | ||
export type MaplibreGeocoderSuggestionResults = { suggestions: { text: string, placeId?: string }[] }; | ||
export type MaplibreGeocoderPlaceResults = { place: CarmenGeojsonFeature[] }; | ||
export type MaplibreGeocoderResults = MaplibreGeocoderFeatureResults | MaplibreGeocoderSuggestionResults | MaplibreGeocoderPlaceResults; | ||
/** | ||
@@ -222,9 +250,9 @@ * An API which contains reverseGeocode and forwardGeocode functions to be used by this plugin | ||
*/ | ||
forwardGeocode: (config: MaplibreGeocoderApiConfig) => Promise<{ features: CarmenGeojsonFeature[] }>; | ||
forwardGeocode: (config: MaplibreGeocoderApiConfig) => Promise<MaplibreGeocoderFeatureResults>; | ||
/** | ||
* Reverse geocode function should return an object including a collection of {@link CarmenGeojsonFeature}. | ||
*/ | ||
reverseGeocode: (config: MaplibreGeocoderApiConfig) => Promise<{ features: CarmenGeojsonFeature[] }>; | ||
getSuggestions?: (config: MaplibreGeocoderApiConfig) => Promise<{ suggestions: { text: string, placeId?: string }[] }>; | ||
searchByPlaceId?: (config: MaplibreGeocoderApiConfig) => Promise<{ place: CarmenGeojsonFeature[] }>; | ||
reverseGeocode: (config: MaplibreGeocoderApiConfig) => Promise<MaplibreGeocoderFeatureResults>; | ||
getSuggestions?: (config: MaplibreGeocoderApiConfig) => Promise<MaplibreGeocoderSuggestionResults>; | ||
searchByPlaceId?: (config: MaplibreGeocoderApiConfig) => Promise<MaplibreGeocoderPlaceResults>; | ||
}; | ||
@@ -575,4 +603,5 @@ | ||
_onKeyDown(e) { | ||
const ESC_KEY_CODE = 27, | ||
TAB_KEY_CODE = 9; | ||
const ESC_KEY_CODE = 27; | ||
const TAB_KEY_CODE = 9; | ||
const ENTER_KEY_CODE = 13; | ||
@@ -606,3 +635,3 @@ if (e.keyCode === ESC_KEY_CODE && this.options.clearAndBlurOnEsc) { | ||
// ENTER | ||
if (e.keyCode === 13) { | ||
if (e.keyCode === ENTER_KEY_CODE) { | ||
if (!this.options.showResultsWhileTyping) { | ||
@@ -772,175 +801,162 @@ if (!this._typeahead.selected) { | ||
_geocode(searchInput: string, isSuggestion = false, isPlaceId = false): Promise<any> { | ||
async _geocode(searchInput: string, isSuggestion = false, isPlaceId = false): Promise<MaplibreGeocoderResults> { | ||
this._loadingEl.style.display = "block"; | ||
this._eventEmitter.emit("loading", { query: searchInput }); | ||
let geocoderError = null; | ||
// Create config object | ||
let config = this._getConfigForRequest(); | ||
const config = this._getConfigForRequest(); | ||
const request = this._createGeocodeRequest(config, searchInput, isSuggestion, isPlaceId); | ||
let request; | ||
const localGeocoderResults = this.options.localGeocoder | ||
? (this.options.localGeocoder(searchInput) || []) | ||
: []; | ||
try { | ||
const response = await request; | ||
await this._handleGeocodeResponse( | ||
response, | ||
config, | ||
searchInput, | ||
isSuggestion, | ||
localGeocoderResults); | ||
} catch (err) { | ||
this._handleGeocodeErrorResponse(err, localGeocoderResults); | ||
} | ||
return request; | ||
} | ||
private _createGeocodeRequest(config: MaplibreGeocoderApiConfig, searchInput: string, isSuggestion: boolean, isPlaceId: boolean): Promise<MaplibreGeocoderResults> { | ||
if (this.options.localGeocoderOnly) { | ||
request = Promise.resolve(); | ||
return Promise.resolve({} as any); | ||
} | ||
// check if searchInput resembles coordinates, and if it does, | ||
// make the request a reverseGeocode | ||
else if ( | ||
this.options.reverseGeocode && | ||
/(-?\d+\.?\d*)[, ]+(-?\d+\.?\d*)[ ]*$/.test(searchInput) | ||
) { | ||
// parse coordinates | ||
const coords = searchInput | ||
.split(/[\s(,)?]+/) | ||
.map((c) => parseFloat(c)) | ||
.reverse(); | ||
if (this.options.reverseGeocode && COORDINATES_REGEXP.test(searchInput)) { | ||
// searchInput resembles coordinates, make the request a reverseGeocode | ||
return this._createReverseGeocodeRequest(searchInput, config); | ||
} | ||
config.query = searchInput; | ||
if (!this.geocoderApi.getSuggestions) { | ||
return this.geocoderApi.forwardGeocode(config); | ||
} | ||
if (!isSuggestion) { | ||
// user typed in text and should receive suggestions | ||
return this.geocoderApi.getSuggestions(config); | ||
} | ||
// user clicked on a suggestion | ||
if (this.geocoderApi.searchByPlaceId && isPlaceId) { | ||
// suggestion has place Id | ||
return this.geocoderApi.searchByPlaceId(config); | ||
} | ||
return this.geocoderApi.forwardGeocode(config); | ||
} | ||
// client only accepts one type for reverseGeocode, so | ||
// use first config type if one, if not default to poi | ||
config = extend(config, { query: coords, limit: 1 }); | ||
private _createReverseGeocodeRequest(searchInput: string, config: MaplibreGeocoderApiConfig) { | ||
// parse coordinates | ||
const coords = searchInput | ||
.split(/[\s(,)?]+/) | ||
.map((c) => parseFloat(c)) | ||
.reverse(); | ||
// drop proximity which may have been set by trackProximity since it's not supported by the reverseGeocoder | ||
if ("proximity" in config) { | ||
delete config.proximity; | ||
} | ||
// client only accepts one type for reverseGeocode, so | ||
// use first config type if one, if not default to poi | ||
config.query = coords; | ||
config.limit = 1; | ||
request = this.geocoderApi.reverseGeocode(config); | ||
} else { | ||
config = extend(config, { query: searchInput }); | ||
if (!this.geocoderApi.getSuggestions) { | ||
request = this.geocoderApi.forwardGeocode(config); | ||
} else { | ||
// user clicked on a suggestion | ||
if (isSuggestion) { | ||
// suggestion has place Id | ||
if (this.geocoderApi.searchByPlaceId && isPlaceId) { | ||
request = this.geocoderApi.searchByPlaceId(config); | ||
} else { | ||
request = this.geocoderApi.forwardGeocode(config); | ||
} | ||
} else { | ||
// user typed in text and should receive suggestions | ||
request = this.geocoderApi.getSuggestions(config); | ||
} | ||
} | ||
// drop proximity which may have been set by trackProximity since it's not supported by the reverseGeocoder | ||
if ("proximity" in config) { | ||
delete config.proximity; | ||
} | ||
let localGeocoderRes = []; | ||
if (this.options.localGeocoder) { | ||
localGeocoderRes = this.options.localGeocoder(searchInput); | ||
if (!localGeocoderRes) { | ||
localGeocoderRes = []; | ||
} | ||
} | ||
let externalGeocoderRes = Promise.resolve([]); | ||
request.catch((error) => { | ||
geocoderError = error; | ||
}).then((response: any) => { | ||
this._loadingEl.style.display = "none"; | ||
return this.geocoderApi.reverseGeocode(config); | ||
} | ||
let res = {} as GeoJSON.FeatureCollection & { config?: MaplibreGeocoderApiConfig }; | ||
private async _handleGeocodeResponse( | ||
response: MaplibreGeocoderResults, | ||
config: MaplibreGeocoderApiConfig, | ||
searchInput: string, | ||
isSuggestion: boolean, | ||
localGeocoderResults: CarmenGeojsonFeature[] | ||
) { | ||
this._loadingEl.style.display = "none"; | ||
if (!response) { | ||
res = { | ||
type: "FeatureCollection", | ||
features: [], | ||
}; | ||
} else { | ||
res = response; | ||
} | ||
let res = {} as MaplibreGeocoderResults & { config?: MaplibreGeocoderApiConfig, features?: CarmenGeojsonFeature[] }; | ||
res.config = config; | ||
if (!response) { | ||
res = { | ||
type: "FeatureCollection", | ||
features: [], | ||
}; | ||
} else { | ||
res = response; | ||
} | ||
if (this.fresh) { | ||
this.fresh = false; | ||
} | ||
res.config = config; | ||
if (this.fresh) { | ||
this.fresh = false; | ||
} | ||
// supplement Maplibre Geocoding API results with locally populated results | ||
res.features = res.features | ||
? localGeocoderRes.concat(res.features) | ||
: localGeocoderRes; | ||
// supplement Maplibre Geocoding API results with locally populated results | ||
res.features = res.features | ||
? localGeocoderResults.concat(res.features) | ||
: localGeocoderResults; | ||
if (this.options.externalGeocoder) { | ||
externalGeocoderRes = | ||
this.options.externalGeocoder( | ||
searchInput, | ||
res.features, | ||
config | ||
) || Promise.resolve([]); | ||
// supplement Geocoding API results with features returned by a promise | ||
return externalGeocoderRes.then( | ||
function (features) { | ||
res.features = res.features | ||
? features.concat(res.features) | ||
: features; | ||
return res; | ||
}, | ||
function () { | ||
// on error, display the original result | ||
return res; | ||
} | ||
); | ||
} | ||
return res; | ||
} | ||
) | ||
.then((res) => { | ||
if (geocoderError) { | ||
throw geocoderError; | ||
} | ||
const externalGeocoderResultsPromise = this.options.externalGeocoder | ||
? (this.options.externalGeocoder(searchInput, res.features, config) || Promise.resolve([])) | ||
: Promise.resolve([]); | ||
// supplement Geocoding API results with features returned by a promise | ||
try { | ||
const features = await externalGeocoderResultsPromise; | ||
res.features = res.features | ||
? features.concat(res.features) | ||
: features; | ||
} catch { | ||
// on error, display the original result | ||
} | ||
// apply results filter if provided | ||
if (this.options.filter && res.features.length) { | ||
res.features = res.features.filter(this.options.filter); | ||
} | ||
// apply results filter if provided | ||
if (this.options.filter && res.features.length) { | ||
res.features = res.features.filter(this.options.filter); | ||
} | ||
let results = []; | ||
if ('suggestions' in res) { | ||
results = res.suggestions; | ||
} else if ('place' in res) { | ||
results = [res.place]; | ||
} else { | ||
results = res.features; | ||
} | ||
let results = []; | ||
if (res.suggestions) { | ||
results = res.suggestions; | ||
} else if (res.place) { | ||
results = [res.place]; | ||
} else { | ||
results = res.features; | ||
} | ||
if (results.length) { | ||
this._clearEl.style.display = "block"; | ||
this._typeahead.update(results); | ||
if ( | ||
(!this.options.showResultsWhileTyping || isSuggestion) && | ||
this.options.showResultMarkers && | ||
(res.features.length > 0 || 'place' in res) | ||
) { | ||
this._fitBoundsForMarkers(); | ||
} | ||
this._eventEmitter.emit("results", res); | ||
} else { | ||
this._clearEl.style.display = "none"; | ||
this._typeahead.selected = null; | ||
this._renderNoResults(); | ||
this._eventEmitter.emit("results", res); | ||
} | ||
} | ||
if (results.length) { | ||
this._clearEl.style.display = "block"; | ||
private _handleGeocodeErrorResponse(error: Error, localGeocoderResults: CarmenGeojsonFeature[]) { | ||
this._loadingEl.style.display = "none"; | ||
this._typeahead.update(results); | ||
if ( | ||
(!this.options.showResultsWhileTyping || isSuggestion) && | ||
this.options.showResultMarkers && | ||
(res.features.length > 0 || res.place) | ||
) { | ||
this._fitBoundsForMarkers(); | ||
} | ||
// in the event of an error in the Geocoding API still display results from the localGeocoder | ||
if (localGeocoderResults.length && this.options.localGeocoder) { | ||
this._clearEl.style.display = "block"; | ||
this._typeahead.update(localGeocoderResults); | ||
} else { | ||
this._clearEl.style.display = "none"; | ||
this._typeahead.selected = null; | ||
this._renderError(); | ||
} | ||
this._eventEmitter.emit("results", res); | ||
} else { | ||
this._clearEl.style.display = "none"; | ||
this._typeahead.selected = null; | ||
this._renderNoResults(); | ||
this._eventEmitter.emit("results", res); | ||
} | ||
}) | ||
.catch((err) => { | ||
this._loadingEl.style.display = "none"; | ||
// in the event of an error in the Geocoding API still display results from the localGeocoder | ||
if ( | ||
(localGeocoderRes.length && this.options.localGeocoder) || | ||
((externalGeocoderRes as any).length && this.options.externalGeocoder) | ||
) { | ||
this._clearEl.style.display = "block"; | ||
this._typeahead.update(localGeocoderRes); | ||
} else { | ||
this._clearEl.style.display = "none"; | ||
this._typeahead.selected = null; | ||
this._renderError(); | ||
} | ||
this._eventEmitter.emit("results", { features: localGeocoderRes }); | ||
this._eventEmitter.emit("error", { error: err }); | ||
}); | ||
return request; | ||
this._eventEmitter.emit("results", { features: localGeocoderResults }); | ||
this._eventEmitter.emit("error", { error }); | ||
} | ||
@@ -996,4 +1012,6 @@ | ||
_onQueryResult(response) { | ||
const results = response; | ||
_onQueryResult(results: MaplibreGeocoderResults) { | ||
if (!('features' in results)) { | ||
return; | ||
} | ||
if (!results.features.length) return; | ||
@@ -1038,5 +1056,5 @@ const result = results.features[0]; | ||
*/ | ||
query(searchInput: string): this { | ||
this._geocode(searchInput).then(this._onQueryResult); | ||
return this; | ||
async query(searchInput: string) { | ||
const results = await this._geocode(searchInput); | ||
this._onQueryResult(results); | ||
} | ||
@@ -1091,3 +1109,3 @@ | ||
const results = this._typeahead.data | ||
.filter(function (result) { | ||
.filter((result) => { | ||
return typeof result === "string" ? false : true; | ||
@@ -1104,6 +1122,5 @@ }) | ||
const bounds = new this._maplibregl.LngLatBounds(); | ||
results.forEach(function (feature) { | ||
for (const feature of results) { | ||
bounds.extend(feature.geometry.coordinates); | ||
}); | ||
} | ||
this._map.fitBounds(bounds, flyOptions); | ||
@@ -1513,2 +1530,20 @@ } | ||
/** | ||
* Subscribe to events that happen within the plugin only once. | ||
* @param type - Event name. | ||
* Available events and the data passed into their respective event objects are: | ||
* | ||
* - __clear__ `Emitted when the input is cleared` | ||
* - __loading__ `{ query } Emitted when the geocoder is looking up a query` | ||
* - __results__ `{ results } Fired when the geocoder returns a response` | ||
* - __result__ `{ result } Fired when input is set` | ||
* - __error__ `{ error } Error as string` | ||
* @returns a Promise that resolves when the event is emitted. | ||
*/ | ||
once(type: string): Promise<any> { | ||
return new Promise((resolve) => { | ||
this._eventEmitter.once(type, resolve); | ||
}); | ||
} | ||
/** | ||
* Remove an event | ||
@@ -1518,3 +1553,3 @@ * @param type - Event name. | ||
*/ | ||
off(type: string, fn): this { | ||
off(type: string, fn: (e: any) => void): this { | ||
this._eventEmitter.removeListener(type, fn); | ||
@@ -1521,0 +1556,0 @@ return this; |
{ | ||
"name": "@maplibre/maplibre-gl-geocoder", | ||
"version": "1.6.0", | ||
"version": "1.7.0", | ||
"description": "A geocoder control for Maplibre GL JS", | ||
@@ -53,3 +53,2 @@ "main": "dist/maplibre-gl-geocoder.js", | ||
"jest-environment-jsdom": "^29.7.0", | ||
"lodash.once": "^4.0.0", | ||
"maplibre-gl": "^4.5.1", | ||
@@ -56,0 +55,0 @@ "rollup": "^4.21.0", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
662232
19
7471
0
1