mirror of
https://github.com/jlengrand/leaflet-geosearch.git
synced 2026-03-10 00:21:20 +00:00
change printWidth to 80 chars
This commit is contained in:
@@ -15,4 +15,9 @@ module.exports = {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
curly: ['error', 'all'],
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,6 +2,6 @@ module.exports = {
|
||||
semi: true,
|
||||
trailingComma: 'all',
|
||||
singleQuote: true,
|
||||
printWidth: 120,
|
||||
printWidth: 80,
|
||||
tabWidth: 2,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
@@ -13,7 +13,9 @@ export function createElement<T extends HTMLElement = HTMLElement>(
|
||||
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<T = object>(url: string, cb: string): Promise<T> {
|
||||
export function createScriptElement<T = object>(
|
||||
url: string,
|
||||
cb: string,
|
||||
): Promise<T> {
|
||||
const script = createElement('script', null, document.body);
|
||||
script.setAttribute('type', 'text/javascript');
|
||||
|
||||
@@ -47,9 +52,13 @@ export function createScriptElement<T = object>(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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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<RequestResult, RawResult> {
|
||||
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<RequestResult, RawRes
|
||||
|
||||
async search({ query }: SearchArgument): Promise<SearchResult<RawResult>[]> {
|
||||
const jsonp = `BING_JSONP_CB_${Date.now()}`;
|
||||
const json = await createScriptElement<RequestResult>(this.endpoint({ query, jsonp }), jsonp);
|
||||
const json = await createScriptElement<RequestResult>(
|
||||
this.endpoint({ query, jsonp }),
|
||||
jsonp,
|
||||
);
|
||||
|
||||
return this.parse({ data: json });
|
||||
}
|
||||
|
||||
@@ -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<RequestResult, RawResult> {
|
||||
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;
|
||||
|
||||
@@ -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<RequestResult, RawResult> {
|
||||
export default class GoogleProvider extends AbstractProvider<
|
||||
RequestResult,
|
||||
RawResult
|
||||
> {
|
||||
searchUrl = 'https://maps.googleapis.com/maps/api/geocode/json';
|
||||
|
||||
endpoint({ query }: EndpointArgument) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import OpenStreetMapProvider, { OpenStreetMapProviderOptions } from './openStreetMapProvider';
|
||||
import OpenStreetMapProvider, {
|
||||
OpenStreetMapProviderOptions,
|
||||
} from './openStreetMapProvider';
|
||||
|
||||
export default class LocationIQProvider extends OpenStreetMapProvider {
|
||||
constructor(options: OpenStreetMapProviderOptions) {
|
||||
|
||||
@@ -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<RequestResult, RawResult> {
|
||||
export default class OpenCageProvider extends AbstractProvider<
|
||||
RequestResult,
|
||||
RawResult
|
||||
> {
|
||||
searchUrl = 'https://api.opencagedata.com/geocode/v1/json';
|
||||
|
||||
endpoint({ query }: EndpointArgument) {
|
||||
|
||||
@@ -30,15 +30,20 @@ export type OpenStreetMapProviderOptions = {
|
||||
reverseUrl?: string;
|
||||
} & ProviderOptions;
|
||||
|
||||
export default class OpenStreetMapProvider extends AbstractProvider<RawResult[], RawResult> {
|
||||
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<RawResult[],
|
||||
}
|
||||
|
||||
parse(response: ParseArgument<RequestResult>): SearchResult<RawResult>[] {
|
||||
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),
|
||||
|
||||
@@ -58,12 +58,16 @@ export default abstract class AbstractProvider<TRequestResult, TRawResult>
|
||||
}
|
||||
|
||||
abstract endpoint(options: EndpointArgument): string;
|
||||
abstract parse(response: ParseArgument<TRequestResult>): SearchResult<TRawResult>[];
|
||||
abstract parse(
|
||||
response: ParseArgument<TRequestResult>,
|
||||
): SearchResult<TRawResult>[];
|
||||
|
||||
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('&');
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,10 @@ export default class ResultList {
|
||||
constructor({ handleClick, classNames = {} }: ResultListProps) {
|
||||
this.handleClick = handleClick;
|
||||
|
||||
this.container = createElement<HTMLDivElement>('div', cx('results', classNames.container));
|
||||
this.container = createElement<HTMLDivElement>(
|
||||
'div',
|
||||
cx('results', classNames.container),
|
||||
);
|
||||
this.container.addEventListener('click', this.onClick, true);
|
||||
|
||||
this.resultItem = createElement<HTMLDivElement>('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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<HTMLDivElement>('div', cx('geosearch', classNames.container));
|
||||
constructor({
|
||||
handleSubmit,
|
||||
searchLabel,
|
||||
classNames = {},
|
||||
}: SearchElementProps) {
|
||||
this.container = createElement<HTMLDivElement>(
|
||||
'div',
|
||||
cx('geosearch', classNames.container),
|
||||
);
|
||||
|
||||
this.form = createElement<HTMLFormElement>('form', ['', classNames.form].join(' '), this.container, {
|
||||
autocomplete: 'none',
|
||||
});
|
||||
this.form = createElement<HTMLFormElement>(
|
||||
'form',
|
||||
['', classNames.form].join(' '),
|
||||
this.container,
|
||||
{
|
||||
autocomplete: 'none',
|
||||
},
|
||||
);
|
||||
|
||||
this.input = createElement<HTMLInputElement>('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<HTMLInputElement>(
|
||||
'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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user