diff --git a/.eslintrc.js b/.eslintrc.js index 184db0f6..16736c3f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,4 +15,9 @@ module.exports = { '@typescript-eslint/no-explicit-any': 'off', curly: ['error', 'all'], }, + settings: { + react: { + version: 'detect', + }, + }, }; diff --git a/.prettierrc.js b/.prettierrc.js index 13c2be1a..a4db989c 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -2,6 +2,6 @@ module.exports = { semi: true, trailingComma: 'all', singleQuote: true, - printWidth: 120, + printWidth: 80, tabWidth: 2, }; diff --git a/src/constants.ts b/src/constants.ts index 05570274..867b15ed 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,4 +5,11 @@ export const ARROW_UP_KEY = 38; export const ARROW_LEFT_KEY = 37; export const ARROW_RIGHT_KEY = 39; -export const SPECIAL_KEYS = [ENTER_KEY, ESCAPE_KEY, ARROW_DOWN_KEY, ARROW_UP_KEY, ARROW_LEFT_KEY, ARROW_RIGHT_KEY]; +export const SPECIAL_KEYS = [ + ENTER_KEY, + ESCAPE_KEY, + ARROW_DOWN_KEY, + ARROW_UP_KEY, + ARROW_LEFT_KEY, + ARROW_RIGHT_KEY, +]; diff --git a/src/domUtils.ts b/src/domUtils.ts index ef04701f..6a8c2f5e 100644 --- a/src/domUtils.ts +++ b/src/domUtils.ts @@ -13,7 +13,9 @@ export function createElement( Object.keys(attributes).forEach((key) => { if (typeof attributes[key] === 'function') { // IE doesn't support startsWith - const type = (key.indexOf('on') === 0 ? key.substr(2).toLowerCase() : key) as keyof HTMLElementEventMap; + const type = (key.indexOf('on') === 0 + ? key.substr(2).toLowerCase() + : key) as keyof HTMLElementEventMap; el.addEventListener(type, attributes[key] as () => void); } else { el.setAttribute(key, attributes[key] as string); @@ -32,7 +34,10 @@ export function stopPropagation(event: Event) { event.stopPropagation(); } -export function createScriptElement(url: string, cb: string): Promise { +export function createScriptElement( + url: string, + cb: string, +): Promise { const script = createElement('script', null, document.body); script.setAttribute('type', 'text/javascript'); @@ -47,9 +52,13 @@ export function createScriptElement(url: string, cb: string): Promis }); } -export const cx = (...classNames: (string | undefined)[]): string => classNames.filter(Boolean).join(' ').trim(); +export const cx = (...classNames: (string | undefined)[]): string => + classNames.filter(Boolean).join(' ').trim(); -export function addClassName(element: Element, className: string | string[]): void { +export function addClassName( + element: Element, + className: string | string[], +): void { if (!element || !element.classList) { return; } @@ -63,7 +72,10 @@ export function addClassName(element: Element, className: string | string[]): vo }); } -export function removeClassName(element: Element, className: string | string[]): void { +export function removeClassName( + element: Element, + className: string | string[], +): void { if (!element || !element.classList) { return; } @@ -77,7 +89,11 @@ export function removeClassName(element: Element, className: string | string[]): }); } -export function replaceClassName(element: Element, find: string, replace: string): void { +export function replaceClassName( + element: Element, + find: string, + replace: string, +): void { removeClassName(element, find); addClassName(element, replace); } diff --git a/src/leafletControl.js b/src/leafletControl.js index cb951502..26bf56d6 100644 --- a/src/leafletControl.js +++ b/src/leafletControl.js @@ -3,7 +3,13 @@ import ResultList from './resultList'; import debounce from './lib/debounce'; import { createElement, addClassName, removeClassName } from './domUtils'; -import { ENTER_KEY, SPECIAL_KEYS, ARROW_UP_KEY, ARROW_DOWN_KEY, ESCAPE_KEY } from './constants'; +import { + ENTER_KEY, + SPECIAL_KEYS, + ARROW_UP_KEY, + ARROW_DOWN_KEY, + ESCAPE_KEY, +} from './constants'; const defaultOptions = () => ({ position: 'topleft', @@ -37,7 +43,14 @@ const defaultOptions = () => ({ }); const wasHandlerEnabled = {}; -const mapHandlers = ['dragging', 'touchZoom', 'doubleClickZoom', 'scrollWheelZoom', 'boxZoom', 'keyboard']; +const mapHandlers = [ + 'dragging', + 'touchZoom', + 'doubleClickZoom', + 'scrollWheelZoom', + 'boxZoom', + 'keyboard', +]; const Control = { initialize(options) { @@ -49,7 +62,13 @@ const Control = { ...options, }; - const { style, classNames, searchLabel, autoComplete, autoCompleteDelay } = this.options; + const { + style, + classNames, + searchLabel, + autoComplete, + autoCompleteDelay, + } = this.options; if (style !== 'button') { this.options.classNames.container += ` ${options.style}`; } @@ -100,7 +119,11 @@ const Control = { true, ); input.addEventListener('keydown', (e) => this.selectResult(e), true); - input.addEventListener('keydown', (e) => this.clearResults(e, true), true); + input.addEventListener( + 'keydown', + (e) => this.clearResults(e, true), + true, + ); } form.addEventListener('mouseenter', (e) => this.disableHandlers(e), true); @@ -120,7 +143,9 @@ const Control = { if (style === 'bar') { const { form } = this.searchElement.elements; - const root = map.getContainer().querySelector('.leaflet-control-container'); + const root = map + .getContainer() + .querySelector('.leaflet-control-container'); const container = createElement('div', 'leaflet-control-geosearch bar'); container.appendChild(form); @@ -186,7 +211,9 @@ const Control = { }, selectResult(event) { - if ([ENTER_KEY, ARROW_DOWN_KEY, ARROW_UP_KEY].indexOf(event.keyCode) === -1) { + if ( + [ENTER_KEY, ARROW_DOWN_KEY, ARROW_UP_KEY].indexOf(event.keyCode) === -1 + ) { return; } @@ -208,7 +235,8 @@ const Control = { } // eslint-disable-next-line no-bitwise - const next = event.code === 'ArrowDown' ? ~~list.selected + 1 : ~~list.selected - 1; + const next = + event.code === 'ArrowDown' ? ~~list.selected + 1 : ~~list.selected - 1; // eslint-disable-next-line no-nested-ternary const idx = next < 0 ? max : next > max ? 0 : next; @@ -325,12 +353,16 @@ const Control = { const { retainZoomLevel, animateZoom } = this.options; const resultBounds = new L.LatLngBounds(result.bounds); - const bounds = resultBounds.isValid() ? resultBounds : this.markers.getBounds(); + const bounds = resultBounds.isValid() + ? resultBounds + : this.markers.getBounds(); if (!retainZoomLevel && resultBounds.isValid()) { this.map.fitBounds(bounds, { animate: animateZoom }); } else { - this.map.setView(bounds.getCenter(), this.getZoom(), { animate: animateZoom }); + this.map.setView(bounds.getCenter(), this.getZoom(), { + animate: animateZoom, + }); } }, @@ -342,7 +374,9 @@ const Control = { export default function LeafletControl(...options) { if (!L || !L.Control || !L.Control.extend) { - throw new Error('Leaflet must be loaded before instantiating the GeoSearch control'); + throw new Error( + 'Leaflet must be loaded before instantiating the GeoSearch control', + ); } const LControl = L.Control.extend(Control); diff --git a/src/lib/hasShape.ts b/src/lib/hasShape.ts index 25da9314..db13d8c7 100644 --- a/src/lib/hasShape.ts +++ b/src/lib/hasShape.ts @@ -1,4 +1,8 @@ -export default function hasShape(keys: string[], exact: boolean, object: object): boolean { +export default function hasShape( + keys: string[], + exact: boolean, + object: object, +): boolean { if (exact && keys.length !== Object.keys(object).length) { return false; } diff --git a/src/providers/__tests__/bingProvider.spec.js b/src/providers/__tests__/bingProvider.spec.js index 22a08251..9816908a 100644 --- a/src/providers/__tests__/bingProvider.spec.js +++ b/src/providers/__tests__/bingProvider.spec.js @@ -21,9 +21,16 @@ describe('BingProvider', () => { const callbackName = `BING_JSONP_CB_${now}`; beforeAll(() => { - fetch.mockResponse(async () => ({ body: `${callbackName}(${JSON.stringify(fixtures)})` })); + fetch.mockResponse(async () => ({ + body: `${callbackName}(${JSON.stringify(fixtures)})`, + })); + // eslint-disable-next-line @typescript-eslint/no-var-requires - jest.spyOn(require('../../domUtils'), 'createScriptElement').mockImplementation(jest.fn(async () => fixtures)); + const domUtils = require('../../domUtils'); + + jest + .spyOn(domUtils, 'createScriptElement') + .mockImplementation(jest.fn(async () => fixtures)); }); afterAll(() => { @@ -43,8 +50,12 @@ describe('BingProvider', () => { const result = results[0]; expect(result.label).toBeTruthy(); - expect(result.x).toEqual(fixtures.resourceSets[0].resources[0].point.coordinates[1]); - expect(result.y).toEqual(fixtures.resourceSets[0].resources[0].point.coordinates[0]); + expect(result.x).toEqual( + fixtures.resourceSets[0].resources[0].point.coordinates[1], + ); + expect(result.y).toEqual( + fixtures.resourceSets[0].resources[0].point.coordinates[0], + ); expect(result.bounds[0][0]).toBeGreaterThan(result.bounds[0][1]); expect(result.bounds[1][0]).toBeGreaterThan(result.bounds[1][1]); expect(result.bounds[0][0]).toBeLessThan(result.bounds[1][0]); diff --git a/src/providers/bingProvider.ts b/src/providers/bingProvider.ts index 7f987431..a128487b 100644 --- a/src/providers/bingProvider.ts +++ b/src/providers/bingProvider.ts @@ -1,4 +1,9 @@ -import AbstractProvider, { EndpointArgument, ParseArgument, SearchArgument, SearchResult } from './provider'; +import AbstractProvider, { + EndpointArgument, + ParseArgument, + SearchArgument, + SearchResult, +} from './provider'; import { createScriptElement } from '../domUtils'; export interface RequestResult { @@ -39,7 +44,10 @@ export interface RawResult { matchCodes: string[]; } -export default class BingProvider extends AbstractProvider { +export default class BingProvider extends AbstractProvider< + RequestResult, + RawResult +> { searchUrl = 'https://dev.virtualearth.net/REST/v1/Locations'; endpoint({ query, jsonp }: EndpointArgument & { jsonp: string }): string { @@ -68,7 +76,10 @@ export default class BingProvider extends AbstractProvider[]> { const jsonp = `BING_JSONP_CB_${Date.now()}`; - const json = await createScriptElement(this.endpoint({ query, jsonp }), jsonp); + const json = await createScriptElement( + this.endpoint({ query, jsonp }), + jsonp, + ); return this.parse({ data: json }); } diff --git a/src/providers/esriProvider.ts b/src/providers/esriProvider.ts index 005a0012..3816b723 100644 --- a/src/providers/esriProvider.ts +++ b/src/providers/esriProvider.ts @@ -1,4 +1,8 @@ -import AbstractProvider, { EndpointArgument, ParseArgument, SearchResult } from './provider'; +import AbstractProvider, { + EndpointArgument, + ParseArgument, + SearchResult, +} from './provider'; interface RequestResult { spatialReference: { wkid: number; latestWkid: number }; @@ -19,8 +23,12 @@ interface RawResult { }; } -export default class EsriProvider extends AbstractProvider { - searchUrl = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find'; +export default class EsriProvider extends AbstractProvider< + RequestResult, + RawResult +> { + searchUrl = + 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find'; endpoint({ query }: EndpointArgument): string { const params = typeof query === 'string' ? { text: query } : query; diff --git a/src/providers/googleProvider.ts b/src/providers/googleProvider.ts index 9fdfdd58..fb50da75 100644 --- a/src/providers/googleProvider.ts +++ b/src/providers/googleProvider.ts @@ -1,4 +1,9 @@ -import AbstractProvider, { EndpointArgument, LatLng, ParseArgument, SearchResult } from './provider'; +import AbstractProvider, { + EndpointArgument, + LatLng, + ParseArgument, + SearchResult, +} from './provider'; export interface RequestResult { results: RawResult[]; @@ -28,7 +33,10 @@ export interface RawResult { types: string[]; } -export default class GoogleProvider extends AbstractProvider { +export default class GoogleProvider extends AbstractProvider< + RequestResult, + RawResult +> { searchUrl = 'https://maps.googleapis.com/maps/api/geocode/json'; endpoint({ query }: EndpointArgument) { diff --git a/src/providers/locationIQProvider.ts b/src/providers/locationIQProvider.ts index 1dfaac63..1706349f 100644 --- a/src/providers/locationIQProvider.ts +++ b/src/providers/locationIQProvider.ts @@ -1,4 +1,6 @@ -import OpenStreetMapProvider, { OpenStreetMapProviderOptions } from './openStreetMapProvider'; +import OpenStreetMapProvider, { + OpenStreetMapProviderOptions, +} from './openStreetMapProvider'; export default class LocationIQProvider extends OpenStreetMapProvider { constructor(options: OpenStreetMapProviderOptions) { diff --git a/src/providers/openCageProvider.ts b/src/providers/openCageProvider.ts index 48436918..824e094f 100644 --- a/src/providers/openCageProvider.ts +++ b/src/providers/openCageProvider.ts @@ -1,4 +1,9 @@ -import AbstractProvider, { EndpointArgument, LatLng, ParseArgument, SearchResult } from './provider'; +import AbstractProvider, { + EndpointArgument, + LatLng, + ParseArgument, + SearchResult, +} from './provider'; export interface RequestResult { results: RawResult[]; @@ -44,8 +49,18 @@ export interface RawResult { speed_in: string; }; sun: { - rise: { apparent: number; astronomical: number; civil: number; nautical: number }; - set: { apparent: number; astronomical: number; civil: number; nautical: number }; + rise: { + apparent: number; + astronomical: number; + civil: number; + nautical: number; + }; + set: { + apparent: number; + astronomical: number; + civil: number; + nautical: number; + }; }; timezone: { name: string; @@ -64,7 +79,10 @@ export interface RawResult { geometry: LatLng; } -export default class OpenCageProvider extends AbstractProvider { +export default class OpenCageProvider extends AbstractProvider< + RequestResult, + RawResult +> { searchUrl = 'https://api.opencagedata.com/geocode/v1/json'; endpoint({ query }: EndpointArgument) { diff --git a/src/providers/openStreetMapProvider.ts b/src/providers/openStreetMapProvider.ts index be808087..f4d34dc7 100644 --- a/src/providers/openStreetMapProvider.ts +++ b/src/providers/openStreetMapProvider.ts @@ -30,15 +30,20 @@ export type OpenStreetMapProviderOptions = { reverseUrl?: string; } & ProviderOptions; -export default class OpenStreetMapProvider extends AbstractProvider { +export default class OpenStreetMapProvider extends AbstractProvider< + RawResult[], + RawResult +> { searchUrl: string; reverseUrl: string; constructor(options: OpenStreetMapProviderOptions = {}) { super(options); - this.searchUrl = options.searchUrl || 'https://nominatim.openstreetmap.org/search'; - this.reverseUrl = options.reverseUrl || 'https://nominatim.openstreetmap.org/reverse'; + this.searchUrl = + options.searchUrl || 'https://nominatim.openstreetmap.org/search'; + this.reverseUrl = + options.reverseUrl || 'https://nominatim.openstreetmap.org/reverse'; } endpoint({ query, type }: EndpointArgument): string { @@ -55,7 +60,9 @@ export default class OpenStreetMapProvider extends AbstractProvider): SearchResult[] { - const records = Array.isArray(response.data) ? response.data : [response.data]; + const records = Array.isArray(response.data) + ? response.data + : [response.data]; return records.map((r) => ({ x: Number(r.lon), diff --git a/src/providers/provider.ts b/src/providers/provider.ts index 2a9d5b00..0c48ce27 100644 --- a/src/providers/provider.ts +++ b/src/providers/provider.ts @@ -58,12 +58,16 @@ export default abstract class AbstractProvider } abstract endpoint(options: EndpointArgument): string; - abstract parse(response: ParseArgument): SearchResult[]; + abstract parse( + response: ParseArgument, + ): SearchResult[]; getParamString(params: ProviderParams = {}): string { const set = { ...this.options.params, ...params }; return Object.keys(set) - .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(set[key])}`) + .map( + (key) => `${encodeURIComponent(key)}=${encodeURIComponent(set[key])}`, + ) .join('&'); } diff --git a/src/resultList.ts b/src/resultList.ts index cc56acf8..a7129fc4 100644 --- a/src/resultList.ts +++ b/src/resultList.ts @@ -20,7 +20,10 @@ export default class ResultList { constructor({ handleClick, classNames = {} }: ResultListProps) { this.handleClick = handleClick; - this.container = createElement('div', cx('results', classNames.container)); + this.container = createElement( + 'div', + cx('results', classNames.container), + ); this.container.addEventListener('click', this.onClick, true); this.resultItem = createElement('div', cx(classNames.item)); @@ -46,7 +49,9 @@ export default class ResultList { select(index: number): SearchResult { // eslint-disable-next-line no-confusing-arrow Array.from(this.container.children).forEach((child, idx) => - idx === index ? addClassName(child, 'active') : removeClassName(child, 'active'), + idx === index + ? addClassName(child, 'active') + : removeClassName(child, 'active'), ); this.selected = index; @@ -72,7 +77,11 @@ export default class ResultList { return; } const target = event.target as HTMLDivElement; - if (!target || !this.container.contains(target) || !target.hasAttribute('data-key')) { + if ( + !target || + !this.container.contains(target) || + !target.hasAttribute('data-key') + ) { return; } diff --git a/src/searchElement.ts b/src/searchElement.ts index 31edbfb0..beff1c6f 100644 --- a/src/searchElement.ts +++ b/src/searchElement.ts @@ -1,4 +1,11 @@ -import { createElement, addClassName, removeClassName, cx, stopPropagation, replaceClassName } from './domUtils'; +import { + createElement, + addClassName, + removeClassName, + cx, + stopPropagation, + replaceClassName, +} from './domUtils'; import { ESCAPE_KEY, ENTER_KEY } from './constants'; interface SearchElementProps { @@ -18,22 +25,39 @@ export default class SearchElement { handleSubmit: (args: { query: string }) => void; hasError = false; - constructor({ handleSubmit, searchLabel, classNames = {} }: SearchElementProps) { - this.container = createElement('div', cx('geosearch', classNames.container)); + constructor({ + handleSubmit, + searchLabel, + classNames = {}, + }: SearchElementProps) { + this.container = createElement( + 'div', + cx('geosearch', classNames.container), + ); - this.form = createElement('form', ['', classNames.form].join(' '), this.container, { - autocomplete: 'none', - }); + this.form = createElement( + 'form', + ['', classNames.form].join(' '), + this.container, + { + autocomplete: 'none', + }, + ); - this.input = createElement('input', ['glass', classNames.input].join(' '), this.form, { - type: 'text', - placeholder: searchLabel || 'search', - onInput: this.onInput, - onKeyUp: this.onKeyUp, - onKeyPress: this.onKeyPress, - onFocus: this.onFocus, - onBlur: this.onBlur, - }); + this.input = createElement( + 'input', + ['glass', classNames.input].join(' '), + this.form, + { + type: 'text', + placeholder: searchLabel || 'search', + onInput: this.onInput, + onKeyUp: this.onKeyUp, + onKeyPress: this.onKeyPress, + onFocus: this.onFocus, + onBlur: this.onBlur, + }, + ); this.handleSubmit = handleSubmit; }