add better docs

This commit is contained in:
Stephan Meijer
2020-04-12 19:27:45 +02:00
parent 9561b34ee7
commit dc733dbfb0
55 changed files with 27056 additions and 8162 deletions

View File

@@ -1,28 +0,0 @@
{
"presets": ["es2015", "power-assert"],
"plugins": [
["fast-async", {
"runtimePattern": null,
"useRuntimeModule": true
}],
["transform-react-jsx", { "pragma": "preact.h"}],
["transform-export-extensions"],
["transform-class-properties"],
["transform-object-rest-spread"],
["babel-plugin-espower", {
"embedAst": true,
"patterns": [
"t.truthy(value, [message])",
"t.falsy(value, [message])",
"t.true(value, [message])",
"t.false(value, [message])",
"t.is(value, expected, [message])",
"t.not(value, expected, [message])",
"t.deepEqual(value, expected, [message])",
"t.notDeepEqual(value, expected, [message])",
"t.regex(contents, regex, [message])",
"t.notRegex(contents, regex, [message])"
]
}]
]
}

16
.eslintrc.js Normal file
View File

@@ -0,0 +1,16 @@
module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'plugin:@typescript-eslint/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended',
'plugin:react/recommended',
],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
rules: {
'@typescript-eslint/ban-ts-ignore': 0,
},
};

View File

@@ -1,55 +0,0 @@
extends: "airbnb"
env:
browser: true
parser: "babel-eslint"
parserOptions:
ecmaVersion: 6
ecmaFeatures:
experimentalObjectRestSpread: true
jsx: true
sourceType: "module"
rules:
# we need this to test leaflet private vars
no-underscore-dangle: "off"
# force else and catch to a new line
brace-style:
- "error"
- "stroustrup"
- allowSingleLine: true
quote-props:
- "error"
- "consistent-as-needed"
- keywords: true
class-methods-use-this: "off"
import/no-extraneous-dependencies:
- "warn"
- devDependencies:
- "**/*.spec.js"
- "**/test_helpers/**/*.js"
- "docs/**/*.js"
react/jsx-filename-extension:
- "error"
- extensions:
- ".js"
react/prop-types: "off"
jsx-a11y/anchor-has-content: "off"
globals:
L: false
fetch: false
document: false
location: false
settings:
react:
pragma: "preact"

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ dist/
.idea .idea
.env .env
.tmp .tmp
.docz

7
.prettierrc.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
semi: true,
trailingComma: 'all',
singleQuote: true,
printWidth: 120,
tabWidth: 2,
};

3
assets/css/docs.css Normal file
View File

@@ -0,0 +1,3 @@
pre .token-line {
height: 1.5em;
}

View File

@@ -1,17 +0,0 @@
.code {
position: absolute;
top: 110px;
right: 10px;
bottom: 10px;
width: 425px;
background: rgba(0, 0, 0, .7);
color: #fff;
border-radius: 4px;
padding: 24px;
font-family: Roboto, monospace;
white-space: pre;
font-size: 14px;
line-height: 22px;
}

View File

@@ -1,31 +0,0 @@
import preact, { Component } from 'preact';
import microlight from 'microlight';
import styles from './Code.css';
class Code extends Component {
componentDidMount() {
microlight.reset('code');
}
componentDidUpdate() {
const { children } = this.props;
this.container.innerHTML = children.join('\n\n');
microlight.reset('code');
}
defineContainer = (ref) => {
this.container = ref;
};
render() {
const { children } = this.props;
return (
<div ref={this.defineContainer} className={styles.code}>
{children}
</div>
);
}
}
export default Code;

View File

@@ -1,60 +0,0 @@
.header {
position: relative;
width: 100%;
height: 100px;
padding: 0 0 0 20px;
}
.header h1 {
font-family: 'Roboto', sans-serif;
font-size: 32px;
font-weight: 300;
line-height: 64px;
margin: 0;
padding: 0;
}
.header ul {
list-style: none;
display: block;
overflow: hidden;
margin: 0;
padding: 0;
line-height: 32px
}
.header ul li {
float: left;
display: block;
cursor: pointer;
}
.header ul li:hover {
border-bottom: 4px solid #00bcd4;
}
.header ul :global(li.active) {
border-bottom: 4px solid #2196f3;
}
.header ul li a {
text-decoration: none;
color: inherit;
display: block;
width: 100%;
height: 100%;
padding: 0 24px;
}
.content {
position: absolute;
top: 100px;
bottom: 0;
left: 20px;
right: 465px;
}
.fullWidth {
left: 0;
right: 0;
}

View File

@@ -1,65 +0,0 @@
import preact, { Component } from 'preact';
import Code from './Code';
import styles from './Layout.css';
class Layout extends Component {
constructor(props) {
super(props);
this.state = {
hash: window.location.hash.slice(1),
};
}
componentDidMount() {
window.addEventListener('hashchange', this.changePage, false);
}
componentWillUnmount() {
window.removeEventListener('hashchange', this.changePage, false);
}
changePage = () => {
this.setState({
hash: window.location.hash.slice(1),
});
};
render() {
const { pages } = this.props;
const { hash } = this.state;
const page = pages.find(p => p.slug === (hash || 'search'));
const contentClassName = hash === 'search'
? styles.content
: [styles.content, styles.fullWidth].join(' ');
return (
<div>
<div className={styles.header}>
<h1>{`GeoSearch / ${page.title}`}</h1>
<ul>
{pages.map((p, idx) => (
<li key={idx} className={p.slug === hash && 'active'}>
<a href={`#${p.slug}`}>{p.title}</a>
</li>
))}
</ul>
</div>
<div className={contentClassName}>
{page && page.view()}
</div>
{page.code && (
<Code>
{page.code}
</Code>
)}
</div>
);
}
}
export default Layout;

View File

@@ -0,0 +1,9 @@
.root {
width: 100%;
height: 300px;
}
.map {
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,67 @@
import React, { ReactElement, useRef, useEffect } from 'react';
import 'leaflet/dist/leaflet.css';
import { Map, TileLayer } from 'react-leaflet';
import styles from './Leaflet.module.css';
import '../../assets/css/leaflet.css';
import useConfigureLeaflet from '../hooks/useConfigureLeaflet';
import * as providers from '../../src/providers';
import GeoSearchControl from '../../src/leafletControl';
import { MapProps } from './Map';
const providerMap = {
Bing: new providers.BingProvider({
params: { key: process.env.GATSBY_BING_API_KEY },
}),
Esri: new providers.EsriProvider(),
Google: new providers.GoogleProvider({
params: { key: process.env.GATSBY_GOOGLE_API_KEY },
}),
LocationIQ: new providers.LocationIQProvider({
params: { key: process.env.GATSBY_LOCATIONIQ_API_KEY },
}),
OpenCage: new providers.OpenCageProvider({
params: { key: process.env.GATSBY_OPENCAGE_API_KEY },
}),
OpenStreetMap: new providers.OpenStreetMapProvider(),
};
function Leaflet(props: MapProps): ReactElement {
const { provider = 'OpenStreetMap' } = props;
const ref = useRef(null);
const control = useRef(null);
const { viewport } = useConfigureLeaflet();
useEffect(() => {
if (ref.current) {
if (!providerMap[provider]) {
throw new Error('unknown provider');
}
control.current = GeoSearchControl({
style: 'bar',
provider: providerMap[provider],
});
ref.current.leafletElement.addControl(control.current);
}
return () => {
if (control.current) {
ref.current.leafletElement.removeControl(control.current);
}
};
}, [ref.current, control.current, provider]);
return (
<div className={styles.root}>
<Map ref={ref} viewport={viewport} className={styles.map}>
<TileLayer url="//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
</Map>
</div>
);
}
export default Leaflet;

View File

@@ -1,6 +0,0 @@
.map {
position: absolute;
display: block;
width: 100%;
height: 100%;
}

View File

@@ -1,64 +0,0 @@
import preact, { Component } from 'preact';
import merge from 'lodash.merge';
import L from 'leaflet';
import styles from './Map.css';
import {
GeoSearchControl,
OpenStreetMapProvider,
Provider as BaseProvider,
} from '../../src';
// eslint-disable-next-line no-confusing-arrow
const ensureInstance = Provider => Provider instanceof BaseProvider ? Provider : new Provider();
// eslint-disable-next-line no-bitwise
const protocol = ~location.protocol.indexOf('http') ? location.protocol : 'https:';
const mapOptions = () => ({
layers: [
new L.TileLayer(`${protocol}//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`, {
maxZoom: 18,
}),
],
center: new L.LatLng(53.2, 5.8),
zoom: 12,
});
class Map extends Component {
componentDidMount() {
const { options, Provider } = this.props;
this.map = this.map || new L.Map(this.container, mapOptions());
const provider = (Provider) ? ensureInstance(Provider) : new OpenStreetMapProvider();
this.searchControl = new GeoSearchControl({
...options,
provider,
}).addTo(this.map);
window.search = this.searchControl;
window.map = this.map;
}
componentDidUpdate() {
this.map.removeControl(this.searchControl);
this.componentDidMount();
}
componentWillUnmount() {
this.map.remove();
}
bindContainer = (container) => {
this.container = container;
};
render() {
return (
<div className={styles.map} ref={this.bindContainer} />
);
}
}
export default Map;

20
docs/components/Map.tsx Normal file
View File

@@ -0,0 +1,20 @@
import React, { ReactElement, lazy, Suspense } from 'react';
import 'leaflet/dist/leaflet.css';
const Leaflet = lazy(() => import('./Leaflet'));
const Loader = () => <div>loading...</div>;
export interface MapProps {
provider: 'OpenStreetMap' | 'Google' | 'Bing';
providerOptions: any;
}
function Map(props: MapProps): ReactElement {
return (
<Suspense fallback={<Loader />}>
<Leaflet provider={props.provider} />
</Suspense>
);
}
export default Map;

View File

@@ -0,0 +1,13 @@
.root > div > :nth-child(1) > :nth-child(2), .root > div > :nth-child(2) {
display: none;
}
.root + div > pre {
height: auto;
display: block;
background-color: var(--theme-ui-colors-playground-bg,#FFFFFF);
border: 1px solid var(--theme-ui-colors-playground-border,#CED4DE);
border-top: 0;
border-radius: 0 0 4px 4px;
margin: 0;
}

View File

@@ -0,0 +1,13 @@
import React from 'react'
import { Playground, PlaygroundProps } from 'docz'
import styles from './Playground.module.css';
export default (props: PlaygroundProps) => {
return (
<div className={styles.root}>
<Playground {...props} />
</div>
)
}

View File

@@ -1,119 +0,0 @@
import preact, { Component } from 'preact';
import debounce from 'lodash.debounce';
import * as providers from '../../src/providers';
import SearchResults from './SearchResults';
import styles from './Search.css';
const specialKeys = ['ArrowDown', 'ArrowUp', 'Escape'];
class Search extends Component {
constructor(props) {
super(props);
const Provider = providers[`${props.provider}Provider`] ||
providers.OpenStreetMapProvider;
this.provider = new Provider();
}
onSubmit = async (event) => {
event.preventDefault();
const { query } = this.state;
const results = await this.provider.search({ query });
this.setState({
results,
});
};
onKeyUp = (event) => {
if (specialKeys.includes(event.code)) {
return;
}
const query = event.target.value;
this.setState({
query,
});
this.autoSearch(event);
};
onKeyDown = (event) => {
if (event.code === 'Escape') {
this.reset();
return;
}
if (event.code !== 'ArrowDown' && event.code !== 'ArrowUp') {
return;
}
event.preventDefault();
const { selected = -1, results } = this.state;
const max = results.length - 1;
// eslint-disable-next-line no-bitwise
const next = (event.code === 'ArrowDown') ? ~~selected + 1 : ~~selected - 1;
// eslint-disable-next-line no-nested-ternary
const idx = (next < 0) ? max : (next > max) ? 0 : next;
this.setState({
selected: idx,
query: results[idx].label,
});
};
onFocus = () => {
this.setState({ isActive: true });
};
onBlur = () => {
this.setState({ isActive: false });
};
autoSearch = debounce((event) => {
this.onSubmit(event);
}, 250);
reset() {
this.setState({
results: [],
selected: -1,
query: '',
});
}
render() {
const { results, selected, query, isActive } = this.state;
const className = [
styles.search,
(isActive ? 'active' : ''),
].join(' ').trim();
return (
<div className={className}>
<form onSubmit={this.onSubmit}>
<input
onKeyUp={this.onKeyUp}
onKeyDown={this.onKeyDown}
onFocus={this.onFocus}
onBlur={this.onBlur}
type="text"
placeholder="search"
value={query}
/>
</form>
{results &&
<SearchResults results={results} selected={selected} />
}
</div>
);
}
}
export default Search;

View File

@@ -25,3 +25,19 @@
text-indent: 18px; text-indent: 18px;
} }
.result > * {
border: 1px solid transparent;
line-height: 32px;
padding: 0 18px;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.result > *:hover,
.result > :global(.active) {
background-color: #f8f8f8;
border-color: #c6c6c6;
}

View File

@@ -0,0 +1,38 @@
import React, { useState, useEffect, ReactElement } from 'react';
import * as providers from '../../src/providers';
import styles from './Search.module.css';
import { MapProps } from './Map';
interface SearchProps {
provider: MapProps['provider'];
providerOptions: MapProps['providerOptions'];
}
function Search(props: SearchProps): ReactElement {
// @ts-ignore
const Provider = providers[`${props.provider}Provider`] || providers.OpenStreetMapProvider;
const provider = new Provider(props.providerOptions || {});
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
provider.search({ query }).then((results: object[]) => setResults(results.slice(0, 5)));
}, [query]);
return (
<div className={styles.search}>
<form>
<input type="text" placeholder="search" value={query} onChange={(e) => setQuery(e.target.value)} />
</form>
<div className={styles.result}>
{results.map((result, idx) => (
<div key={idx}>{result.label}</div>
))}
</div>
</div>
);
}
export default Search;

View File

@@ -1,15 +0,0 @@
.item > * {
border: 1px solid transparent;
line-height: 32px;
padding: 0 18px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item > *:hover,
.item > :global(.active) {
background-color: #f8f8f8;
border-color: #c6c6c6;
}

View File

@@ -1,12 +0,0 @@
import preact from 'preact';
import styles from './SearchResults.css';
const SearchResults = ({ results = [], selected }) => (
<div className={styles.item}>
{results.map((result, idx) => (
<div className={idx === selected && 'active'}>{result.label}</div>
))}
</div>
);
export default SearchResults;

View File

@@ -1,3 +0,0 @@
export { default as Layout } from './Layout';
export { default as Map } from './Map';
export { default as Search } from './Search';

File diff suppressed because one or more lines are too long

1
docs/dist/style.css vendored
View File

@@ -1 +0,0 @@
.Code__code___31Vg8{position:absolute;top:110px;right:10px;bottom:10px;width:425px;background:rgba(0,0,0,.7);color:#fff;border-radius:4px;padding:24px;font-family:Roboto,monospace;white-space:pre;font-size:14px;line-height:22px}.Layout__header___33oX6{position:relative;width:100%;height:100px;padding:0 0 0 20px}.Layout__header___33oX6 h1{font-family:Roboto,sans-serif;font-size:32px;font-weight:300;line-height:64px;margin:0;padding:0}.Layout__header___33oX6 ul{list-style:none;display:block;overflow:hidden;margin:0;padding:0;line-height:32px}.Layout__header___33oX6 ul li{float:left;display:block;cursor:pointer}.Layout__header___33oX6 ul li:hover{border-bottom:4px solid #00bcd4}.Layout__header___33oX6 ul li.active{border-bottom:4px solid #2196f3}.Layout__header___33oX6 ul li a{text-decoration:none;color:inherit;display:block;width:100%;height:100%;padding:0 24px}.Layout__content___GDRpI{position:absolute;top:100px;bottom:0;left:20px;right:465px}.Layout__fullWidth___2Qlah{left:0;right:0}.Map__map___2UbOE{position:absolute;display:block;width:100%;height:100%}.SearchResults__item___3yUT->*{border:1px solid transparent;line-height:32px;padding:0 18px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.SearchResults__item___3yUT->.active,.SearchResults__item___3yUT->:hover{background-color:#f8f8f8;border-color:#c6c6c6}.Search__search___2kQjw form{position:relative;margin:32px 0;background-color:#fff;vertical-align:top;border-radius:2px;box-shadow:0 2px 2px 0 rgba(0,0,0,.16),0 0 0 1px rgba(0,0,0,.08);transition:box-shadow .2s cubic-bezier(.4,0,.2,1)}.Search__search___2kQjw.active form,.Search__search___2kQjw form:hover{box-shadow:0 3px 8px 0 rgba(0,0,0,.2),0 0 0 1px rgba(0,0,0,.08)}.Search__search___2kQjw input{border:none;padding:0;margin:0;width:100%;outline:none;font:16px arial,sans-serif;line-height:48px;height:48px;text-indent:18px}body,html{font-family:Open Sans,sans-serif;margin:0;padding:0;height:100%;width:100%}*,:after,:before,body,html{box-sizing:border-box}.leaflet-control-geosearch.bar{position:absolute!important;left:50px;right:515px}

View File

@@ -0,0 +1,34 @@
import L from 'leaflet';
import isDomAvailable from '../lib/isDomAvailable';
import { Viewport } from 'react-leaflet';
const viewport: Viewport = {
center: [53.2, 5.8],
zoom: 12,
};
export interface LeafletConfig {
viewport: Viewport;
}
const useConfigureLeaflet = (): LeafletConfig => {
if (!isDomAvailable()) return;
// To get around an issue with the default icon not being set up right between using React
// and importing the leaflet library, we need to reset the image imports
// See https://github.com/PaulLeCam/react-leaflet/issues/453#issuecomment-410450387
// @ts-ignore
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
});
return { viewport };
};
export default useConfigureLeaflet;

View File

@@ -1,24 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet.GeoSearch / Google Provider</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@0.7.7/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@0.7.7/dist/leaflet.js"></script>
<script type="text/javascript">
handleOnLoad = function() {
L.Icon.Default.imagePath = 'https://unpkg.com/leaflet@0.7.7/dist/images/';
}
</script>
<link rel="stylesheet" href="dist/style.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-geosearch@latest/assets/css/leaflet.css" />
</head>
<body onload="handleOnLoad()">
<div id="app"></div>
<script type="text/javascript" src="dist/bundle.min.js" charset="utf-8"></script>
</body>
</html>

44
docs/introduction.mdx Normal file
View File

@@ -0,0 +1,44 @@
---
name: Introduction
route: /
---
import { Link } from 'docz';
import Playground from './components/Playground';
import Map from './components/Map';
# Introduction
`leaflet-geosearch` adds support for geocoding (address lookup, a.k.a. geoseaching) to your (web) application. It comes with controls to be embedded in your Leaflet map.
We support the following providers out-of-the-box; [Bing](/providers/bing), [Esri](/providers/esri), [Google](/providers/google), [OpenStreetMap](/providers/openstreetmap), [LocationIQ](/providers/locationiq), [OpenCage](/providers/opencage).
Although this project is named `leaflet-geosearch`, this library is also usable without LeafletJS, and does not have any dependencies on Leaflet whatsoever.
<Playground>
<Map />
</Playground>
## Installation
```bash
npm install --save leaflet-geosearch
```
## Providers
`leaflet-geosearch` uses so-called "providers" to take care of building the correct service URL and parsing the retrieved data into a uniform format. Thanks to this architecture, it is trivial to add your own providers, so you can use your own geocoding service.
When [`OpenStreetMap`](/providers/openstreet) does not match your needs; you can also choose to use the [`Bing`](/providers/bing), [`Esri`](/providers/esri), [`Google`](/providers/google), [`LocationIQ`](/providers/locationiq), or [`OpenCage`](/providers/opencage) providers. Most of those providers do however require `API keys`. See the documentation pages on the relevant organisations on how to obtain these keys.
In case you decide to write your own provider, please consider submitting a PR to share your work with us.
Providers are unaware of any options you can give them. They are simple proxies to their endpoints. There is only one special property, and that is the `params` option. The difference being; that `params` will be included in the endpoint url. Being Often used for `API KEYS`, while the other attributes can be used for provider configuration.
## Browser support / Polyfills
This project is written with the latest technologies in mind. Thereby it is required to include some polyfills when you wish to support older browsers. These polyfills are recommended for IE and Safari support:
- [babel-polyfill][1], for `array.includes` support.
- [unfetch][2], for `fetch` requests.
[1]: https://npm.im/babel-polyfill
[2]: https://npm.im/unfetch

View File

@@ -1,24 +0,0 @@
// polyfills
import 'babel-polyfill';
import 'whatwg-fetch';
import preact, { render } from 'preact';
import { Layout } from './components';
import pages from './pages';
import './assets/css/style.css';
// import css to enable hot reloading
if (process.env.NODE_ENV !== 'production') {
/* eslint-disable global-require */
require('./index.html');
require('../assets/css/leaflet.css');
}
if (location.hash === '') {
location.hash = 'search';
}
render((
<Layout pages={pages} />
), document.getElementById('app'));

View File

@@ -1,58 +0,0 @@
import preact from 'preact';
import { Search, Map } from './components';
import {
OpenStreetMapProvider,
GoogleProvider,
BingProvider,
EsriProvider,
} from '../src/providers';
const BING_KEY = 'AtUDjSVEBxo8BwgYUPdfnzHpznaYwDdjjS27jyFDj18nhTUDUjrhc0NwMndZvrXs';
const GOOGLE_KEY = 'AIzaSyDigZ5WMPoTj_gnkUn3p1waYPDa5oE8WOw';
/* eslint-disable import/no-webpack-loader-syntax, global-require, import/no-unresolved */
export default [
{
slug: 'search',
title: 'Search',
view: () => (<Search />),
code: require('!!raw!./snippets/search'),
},
{
slug: 'openstreetmap',
title: 'OpenStreetMap',
view: () => (<Map Provider={OpenStreetMapProvider} options={{ style: 'bar' }} />),
code: require('!!raw!./snippets/openstreetmap'),
},
{
slug: 'google',
title: 'Google',
view: () => {
const Provider = new GoogleProvider({ params: {
key: GOOGLE_KEY,
} });
return <Map Provider={Provider} />;
},
code: require('!!raw!./snippets/google'),
},
{
slug: 'bing',
title: 'Bing',
view: () => {
const Provider = new BingProvider({ params: {
key: BING_KEY,
} });
return <Map Provider={Provider} />;
},
code: require('!!raw!./snippets/bing'),
},
{
slug: 'esri',
title: 'Esri',
view: () => (<Map Provider={EsriProvider} />),
code: require('!!raw!./snippets/esri'),
},
];

38
docs/providers/bing.mdx Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bing
menu: Providers
route: /providers/bing
---
import Playground from '../components/Playground';
import Map from '../components/Map';
# Bing Provider
**note**: Bing services require an API key. [Obtain here][1].
For more options and configurations, see the [Microsoft developer docs][2].
<Playground>
<Map provider="Bing" />
</Playground>
```js
import { BingProvider } from 'leaflet-geosearch';
const provider = new BingProvider({
params: {
key: '__YOUR_BING_KEY__'
},
});
// add to leaflet
import { GeoSearchControl } from 'leaflet-geosearch';
map.addControl(new GeoSearchControl({
provider,
}));
```
[1]: https://docs.microsoft.com/en-us/bingmaps/getting-started/bing-maps-dev-center-help/getting-a-bing-maps-key
[2]: https://docs.microsoft.com/en-us/bingmaps/rest-services/locations/find-a-location-by-address

31
docs/providers/esri.mdx Normal file
View File

@@ -0,0 +1,31 @@
---
name: Esri
menu: Providers
route: /providers/esri
---
import Playground from '../components/Playground';
import Map from '../components/Map';
# Esri Provider
For more options and configurations, see the [ArcGIS developer docs][1].
<Playground>
<Map provider="Esri" />
</Playground>
```js
import { EsriProvider } from 'leaflet-geosearch';
const provider = new EsriProvider();
// add to leaflet
import { GeoSearchControl } from 'leaflet-geosearch';
map.addControl(new GeoSearchControl({
provider,
}));
```
[1]: https://developers.arcgis.com/rest/geocode/api-reference/overview-world-geocoding-service.htm

38
docs/providers/google.mdx Normal file
View File

@@ -0,0 +1,38 @@
---
name: Google
menu: Providers
route: /providers/google
---
import Playground from '../components/Playground';
import Map from '../components/Map';
# Google Provider
**note**: Google services require an API key. [Obtain here][1].
For more options and configurations, see the [Google Maps developer docs][2].
<Playground>
<Map provider="Google" />
</Playground>
```js
import { GoogleProvider } from 'leaflet-geosearch';
const provider = new GoogleProvider({
params: {
key: '__YOUR_GOOGLE_KEY__'
},
});
// add to leaflet
import { GeoSearchControl } from 'leaflet-geosearch';
map.addControl(new GeoSearchControl({
provider,
}));
```
[1]: https://developers.google.com/maps/documentation/javascript/get-api-key
[2]: https://developers.google.com/maps/documentation/geocoding/start

View File

@@ -0,0 +1,38 @@
---
name: LocationIQ
menu: Providers
route: /providers/locationiq
---
import Playground from '../components/Playground';
import Map from '../components/Map';
# LocationIQ Provider
**note**: LocationIQ services require an API key. [Obtain here][1].
For more options and configurations, see the [LocationIQ developer docs][2].
<Playground>
<Map provider="LocationIQ" />
</Playground>
```js
import { LocationIQProvider } from 'leaflet-geosearch';
const provider = new LocationIQProvider({
params: {
key: '__YOUR_LOCATIONIQ_KEY__'
},
});
// add to leaflet
import { GeoSearchControl } from 'leaflet-geosearch';
map.addControl(new GeoSearchControl({
provider,
}));
```
[1]: https://locationiq.org
[2]: https://locationiq.org/#docs

View File

@@ -0,0 +1,38 @@
---
name: OpenCage
menu: Providers
route: /providers/opencage
---
import Playground from '../components/Playground';
import Map from '../components/Map';
# OpenCage Provider
**note**: OpenCage services require an API key. [Obtain here][1].
For more options and configurations, see the [OpenCage developer docs][2].
<Playground>
<Map provider="OpenCage" />
</Playground>
```js
import { OpenCageProvider } from 'leaflet-geosearch';
const provider = new OpenCageProvider({
params: {
key: '__YOUR_OPENCAGE_KEY__'
},
});
// add to leaflet
import { GeoSearchControl } from 'leaflet-geosearch';
map.addControl(new GeoSearchControl({
provider,
}));
```
[1]: https://geocoder.opencagedata.com
[2]: https://geocoder.opencagedata.com/api

View File

@@ -0,0 +1,32 @@
---
name: OpenStreetMap
menu: Providers
route: /providers/openstreetmap
---
import Playground from '../components/Playground';
import Map from '../components/Map';
# OpenStreetMap Provider
For more options and configurations, see the [OpenStreetMap Nominatim wiki][1].
<Playground>
<Map provider="OpenStreetMap" />
</Playground>
```js
import { OpenStreetMapProvider } from 'leaflet-geosearch';
const provider = new OpenStreetMapProvider();
// add to leaflet
import { GeoSearchControl } from 'leaflet-geosearch'; import {OpenStreetMapProvider} from "./index";
map.addControl(new GeoSearchControl({
provider,
}));
```
[1]: http://wiki.openstreetmap.org/wiki/Nominatim

View File

@@ -1,16 +0,0 @@
import L from 'leaflet';
import {
GeoSearchControl,
BingProvider,
} from 'leaflet-geosearch';
const provider = new BingProvider({ params: {
key: '__YOUR_BING_KEY__'
} });
const searchControl = new GeoSearchControl({
provider: provider,
});
const map = new L.Map('map');
map.addControl(searchControl);

View File

@@ -1,14 +0,0 @@
import L from 'leaflet';
import {
GeoSearchControl,
EsriProvider,
} from 'leaflet-geosearch';
const provider = new EsriProvider();
const searchControl = new GeoSearchControl({
provider: provider,
});
const map = new L.Map('map');
map.addControl(searchControl);

View File

@@ -1,16 +0,0 @@
import L from 'leaflet';
import {
GeoSearchControl,
GoogleProvider,
} from 'leaflet-geosearch';
const provider = new GoogleProvider({ params: {
key: '__YOUR_GOOGLE_KEY__',
} });
const searchControl = new GeoSearchControl({
provider: provider,
});
const map = new L.Map('map');
map.addControl(searchControl);

View File

@@ -1,14 +0,0 @@
import L from 'leaflet';
import {
GeoSearchControl,
OpenStreetMapProvider,
} from 'leaflet-geosearch';
const provider = new OpenStreetMapProvider();
const searchControl = new GeoSearchControl({
provider: provider,
});
const map = new L.Map('map');
map.addControl(searchControl);

View File

@@ -1,16 +0,0 @@
import {
OpenStreetMapProvider,
} from 'leaflet-geosearch';
const provider = new OpenStreetMapProvider();
const form = document.querySelector('form');
const input = form.querySelector('input[type="text"]');
form.addEventListener('submit', (event) => {
event.preventDefault();
provider.search({ query: input.value }).then((results) => {
console.log(results);
});
});

62
docs/usage.mdx Normal file
View File

@@ -0,0 +1,62 @@
---
name: Usage
route: /usage
---
import Playground from './components/Playground';
import Map from './components/Map';
import Search from './components/Search';
# Usage
There are two ways in which `leaflet-geosearch` can be used. Direct usage, for example for address forms, or embedded in a leaflet map to search for points of interest.
## Using the providers directly
All providers can be used without leaflet. You might want to bind them to a form;
<Playground>
<Search provider="OpenStreetMap" />
</Playground>
```js
import { OpenStreetMapProvider } from 'leaflet-geosearch';
const provider = new OpenStreetMapProvider();
const results = await provider.search({ query: input.value });
```
## Using the leaflet control
Or add the `GeoSearchControl` to the leaflet map instance, to render a search control on your map;
<Playground>
<Map provider="OpenStreetMap" />
</Playground>
```js
import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch';
const searchControl = new GeoSearchControl({
provider: new OpenStreetMapProvider(),
});
map.addControl(searchControl);
```
## Results
The search event of all providers return an array of `result` objects. The base structure is uniform between the providers. It contains objects matching the following interface:
```ts
interface result {
x: number; // lon
y: number; // lat
label: string; // formatted address
bounds: [
[number, number], // south, west - lat, lon
[number, number], // north, east - lat, lon
],
raw: any, // raw provider result
}
```

16
doczrc.js Normal file
View File

@@ -0,0 +1,16 @@
export default {
title: 'Leaflet GeoSearch',
typescript: true,
themeConfig: {
showPlaygroundEditor: true,
},
src: 'docs',
dest: '.docz/dist',
public: 'assets',
ignore: ['CODE_OF_CONDUCT.md', 'CONTRIBUTING.md', 'LICENSE.md'],
menu: [
{ name: 'Introduction' },
{ name: 'Usage' },
{ name: 'Providers', menu: ['Bing', 'Esri', 'Google', 'LocationIQ', 'OpenCage', 'OpenStreetMap'] },
],
};

1
gatsby-browser.js Normal file
View File

@@ -0,0 +1 @@
import './static/assets/css/docs.css';

3
gatsby-config.js Normal file
View File

@@ -0,0 +1,3 @@
require('dotenv').config({
path: '.env',
});

16
gatsby-node.js Normal file
View File

@@ -0,0 +1,16 @@
exports.onCreateWebpackConfig = ({ stage, rules, loaders, actions }) => {
switch (stage) {
case 'build-html':
actions.setWebpackConfig({
module: {
rules: [
{
test: /react-leaflet|leaflet/,
use: [loaders.null()],
},
],
},
});
break;
}
};

26408
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,22 +8,11 @@
"main": "lib/index.js", "main": "lib/index.js",
"module": "src/index.js", "module": "src/index.js",
"scripts": { "scripts": {
"build:commonjs": "babel src --out-dir lib --ignore *.spec.js", "lint": "tsc --noEmit && eslint 'src/**/*.{js,ts,tsx}' --quiet --fix",
"build:umd": "cross-env NODE_ENV=development webpack", "docz:dev": "docz dev",
"build:umd:min": "cross-env NODE_ENV=production webpack", "docz:build": "docz build",
"build:watch": "npm run build:umd -- --watch", "docz:serve": "docz build && docz serve",
"build": "npm run clean && npm run build:commonjs && npm run build:umd && npm run build:umd:min && npm run build:docs", "docz:publish": "git stash && git checkout -b gh-pages && git checkout stash@{0} -- ./docs && git add . && git commit -m \"update docs\" && git checkout - && git stash pop"
"build:docs": "cross-env NODE_ENV=production webpack --config webpack.docs.config.babel.js",
"clean": "rimraf lib dist",
"lint": "esw src webpack.config --color",
"lint:fix": "npm run lint -- --fix",
"lint:watch": "npm run lint -- --watch",
"prepublish": "npm run lint && npm run test && npm run build",
"test": "ava",
"test:watch": "ava -w",
"test:cover": "nyc ava",
"test:report": "nyc report --reporter html",
"start": "webpack-dev-server"
}, },
"files": [ "files": [
"src", "src",
@@ -34,6 +23,9 @@
"directories": { "directories": {
"example": "example" "example": "example"
}, },
"resolutions": {
"docz/**/webpack": "4.28.4"
},
"keywords": [ "keywords": [
"geolocation", "geolocation",
"geocoding", "geocoding",
@@ -53,59 +45,25 @@
"homepage": "https://github.com/smeijer/leaflet-geosearch#readme", "homepage": "https://github.com/smeijer/leaflet-geosearch#readme",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"ava": "^0.17.0", "@types/lodash.debounce": "^4.0.6",
"babel-cli": "^6.18.0", "@types/react-leaflet": "^2.5.1",
"babel-core": "^6.21.0", "@typescript-eslint/eslint-plugin": "^2.27.0",
"babel-eslint": "^7.1.1", "@typescript-eslint/parser": "^2.27.0",
"babel-loader": "^6.2.10", "docz": "^2.3.1",
"babel-plugin-transform-class-properties": "^6.19.0", "docz-theme-default": "^1.2.0",
"babel-plugin-transform-export-extensions": "^6.8.0", "eslint": "^6.8.0",
"babel-plugin-transform-object-rest-spread": "^6.20.2", "eslint-config-prettier": "^6.10.1",
"babel-plugin-transform-react-jsx": "^6.8.0", "eslint-plugin-prettier": "^3.1.2",
"babel-polyfill": "^6.23.0", "eslint-plugin-react": "^7.19.0",
"babel-preset-es2015": "^6.18.0", "gatsby-plugin-react-leaflet": "^2.0.12",
"babel-preset-power-assert": "^1.0.0", "leaflet": "^1.6.0",
"browser-env": "^2.0.19", "prettier": "^2.0.4",
"cross-env": "^3.1.3", "react": "^16.13.1",
"css-loader": "^0.26.1", "react-dom": "^16.13.1",
"dotenv": "^2.0.0", "react-leaflet": "^2.6.3",
"eslint": "^3.12.2", "typescript": "^3.8.3"
"eslint-config-airbnb": "^13.0.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.8.0",
"eslint-watch": "^2.1.14",
"extract-text-webpack-plugin": "^1.0.1",
"fast-async": "^6.1.2",
"leaflet": "^1.0.2",
"lodash.merge": "^4.6.0",
"microlight": "0.0.7",
"node-fetch": "^1.6.3",
"npm": "^4.0.5",
"power-assert": "^1.4.2",
"preact": "^8.1.0",
"raw-loader": "^0.5.1",
"rimraf": "^2.5.4",
"style-loader": "^0.13.1",
"testdouble": "^1.10.1",
"webpack": "^1.14.0",
"webpack-dev-server": "^3.1.11",
"whatwg-fetch": "^2.0.3"
}, },
"dependencies": { "dependencies": {
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8"
"nodent-runtime": "^3.0.4"
},
"ava": {
"files": [
"./src/**/__tests__/**.spec.js"
],
"require": [
"nodent-runtime",
"dotenv/config",
"babel-core/register",
"./test/browserEnv"
],
"babel": "inherit"
} }
} }

View File

@@ -2,4 +2,7 @@ export { default as BingProvider } from './bingProvider';
export { default as EsriProvider } from './esriProvider'; export { default as EsriProvider } from './esriProvider';
export { default as GoogleProvider } from './googleProvider'; export { default as GoogleProvider } from './googleProvider';
export { default as OpenStreetMapProvider } from './openStreetMapProvider'; export { default as OpenStreetMapProvider } from './openStreetMapProvider';
export { default as OpenCageProvider } from './openCageProvider';
export { default as LocationIQProvider } from './locationIQProvider';
export { default as Provider } from './provider'; export { default as Provider } from './provider';

24
tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "./dist/tsc.js",
"sourceMap": true,
"noEmit": true,
"allowJs": true,
"esModuleInterop": true,
"noUnusedLocals": true,
"jsx": "preserve"
},
"include": [
"src/**/*",
"docs/**/*",
"typings"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}

1
typings/declarations.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module '*.module.css';

View File

@@ -1,36 +0,0 @@
/* eslint-disable no-unused-vars, global-require, import/no-extraneous-dependencies */
const fs = require('fs');
process.env.BABEL_ENV = 'test';
process.env.NODE_ENV = 'production';
module.exports = wallaby => ({
files: [
'src/**/*.js',
'test/**/*.js',
'!src/**/__tests__/**/*.spec.js',
],
tests: [
'src/**/__tests__/**/*.spec.js',
],
compilers: {
'**/*.js': wallaby.compilers.babel(),
},
env: {
type: 'node',
params: {
env: [
'NODE_ENV=production',
].join(';'),
},
},
debug: false,
testFramework: 'ava',
setup: () => {
require('dotenv').config({
path: `${wallaby.localProjectDir}/.env`,
});
require('./test/browserEnv');
},
});

View File

@@ -1,73 +0,0 @@
import webpack from 'webpack';
import path from 'path';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
const { NODE_ENV } = process.env;
const production = NODE_ENV === 'production';
const plugins = [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
}),
new ExtractTextPlugin('style.css', { allChunks: true }),
];
if (production) {
plugins.push(
new webpack.optimize.UglifyJsPlugin({
compressor: {
pure_getters: true,
unsafe: true,
unsafe_comps: true,
screw_ie8: true,
warnings: false,
},
}),
);
}
const entryFiles = [
'nodent-runtime',
path.join(__dirname, 'src/index.js'),
];
if (!production) {
entryFiles.push(path.join(__dirname, 'docs/main.js'));
}
export default {
entry: entryFiles,
output: {
path: path.join(__dirname, 'dist'),
filename: `bundle${production ? '.min' : ''}.js`,
library: 'GeoSearch',
libraryTarget: 'umd',
},
devTool: 'inline-source-map',
devServer: {
// contentBase: './example',
inline: true,
},
module: {
loaders: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract(
'style-loader',
'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
),
},
{
test: /\.html$/,
loader: 'raw-loader',
},
{
test: /\.js$/,
loaders: ['babel-loader'],
exclude: /node_modules/,
},
],
},
plugins,
};

View File

@@ -1,67 +0,0 @@
import webpack from 'webpack';
import path from 'path';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
const { NODE_ENV } = process.env;
const production = NODE_ENV === 'production';
const plugins = [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
}),
new ExtractTextPlugin('style.css', { allChunks: true }),
];
if (production) {
plugins.push(
new webpack.optimize.UglifyJsPlugin({
compressor: {
pure_getters: true,
unsafe: true,
unsafe_comps: true,
screw_ie8: true,
warnings: false,
},
}),
);
}
const entryFiles = [
'nodent-runtime',
path.join(__dirname, 'docs/main.js'),
];
export default {
entry: entryFiles,
output: {
path: path.join(__dirname, 'docs/dist'),
filename: `bundle${production ? '.min' : ''}.js`,
},
devTool: 'inline-source-map',
devServer: {
// contentBase: './example',
inline: true,
},
module: {
loaders: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract(
'style-loader',
'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
),
},
{
test: /\.html$/,
loader: 'raw-loader',
},
{
test: /\.js$/,
loaders: ['babel-loader'],
exclude: /node_modules/,
},
],
},
plugins,
};

7250
yarn.lock

File diff suppressed because it is too large Load Diff