Skip to content

[EXPERIMENTAL] replace map-div with gmp-map custom element #289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/basic-map/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const API_KEY =
globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string);

const App = () => (
<APIProvider apiKey={API_KEY}>
<APIProvider apiKey={API_KEY} version={'beta'}>
<Map
defaultZoom={3}
defaultCenter={{lat: 22.54992, lng: 0}}
Expand Down
38 changes: 38 additions & 0 deletions examples/ecl-compat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Basic Google Maps Setup Example

![image](https://user-images.githubusercontent.com/39244966/208682692-d5b23518-9e51-4a87-8121-29f71e41c777.png)

This is an example to show how to setup a simple Google Maps Map with the `<Map/>` component of the Google Maps React
library.

## Google Maps API key

This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform.
See [the official documentation][get-api-key] on how to create and configure your own key.

The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a
file named `.env` in the example directory with the following content:

```shell title=".env"
GOOGLE_MAPS_API_KEY="<YOUR API KEY HERE>"
```

If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets)

## Development

Go into the example-directory and run

```shell
npm install
```

To start the example with the local library run

```shell
npm run start-local
```

The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example)

[get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key
31 changes: 31 additions & 0 deletions examples/ecl-compat/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title>Example:</title>

<style>
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module">
import '@vis.gl/react-google-maps/examples.css';
import '@vis.gl/react-google-maps/examples.js';
import {renderToDom} from './src/app';

renderToDom(document.querySelector('#app'));
</script>
</body>
</html>
15 changes: 15 additions & 0 deletions examples/ecl-compat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"type": "module",
"dependencies": {
"@googlemaps/extended-component-library": "^0.6.9",
"@vis.gl/react-google-maps": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"vite": "^5.0.4"
},
"scripts": {
"start": "vite",
"start-local": "vite --config ../vite.config.local.js",
"build": "vite build"
}
}
37 changes: 37 additions & 0 deletions examples/ecl-compat/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import {createRoot} from 'react-dom/client';
import {APIProvider, Map} from '@vis.gl/react-google-maps';
import {RouteOverview} from '@googlemaps/extended-component-library/react';
import ControlPanel from './control-panel';

const API_KEY =
globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string);

const App = () => (
<APIProvider apiKey={API_KEY} version={'beta'}>
<Map
mapId={'49ae42fed52588c3'}
defaultCenter={{lat: 53.55, lng: 10.05}}
defaultZoom={10}
gestureHandling={'greedy'}
disableDefaultUI={true}>
<RouteOverview
originAddress={'Little Island, New York'}
destinationAddress={'Times Square Plaza, New York'}
travelMode={'walking'}
noPin></RouteOverview>
</Map>
<ControlPanel />
</APIProvider>
);
export default App;

export function renderToDom(container: HTMLElement) {
const root = createRoot(container);

root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}
28 changes: 28 additions & 0 deletions examples/ecl-compat/src/control-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as React from 'react';

function ControlPanel() {
return (
<div className="control-panel">
<h3>Example Template</h3>
<p>
Add a brief description of the example here and update the link below
</p>

<div className="links">
<a
href="https://codesandbox.io/s/github/visgl/react-google-maps/tree/main/examples/_template"
target="_new">
Try on CodeSandbox ↗
</a>

<a
href="https://github.com/visgl/react-google-maps/tree/main/examples/_template"
target="_new">
View Code ↗
</a>
</div>
</div>
);
}

export default React.memo(ControlPanel);
17 changes: 17 additions & 0 deletions examples/ecl-compat/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {defineConfig, loadEnv} from 'vite';

export default defineConfig(({mode}) => {
const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), '');

return {
define: {
'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY)
},
resolve: {
alias: {
'@vis.gl/react-google-maps/examples.js':
'https://visgl.github.io/react-google-maps/scripts/examples.js'
}
}
};
});
2 changes: 1 addition & 1 deletion examples/multiple-maps/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const App = () => {
}, []);

return (
<APIProvider apiKey={API_KEY}>
<APIProvider apiKey={API_KEY} version={'beta'}>
<div
style={{
height: '100%',
Expand Down
29 changes: 26 additions & 3 deletions src/components/map/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* eslint-disable complexity */
import React, {
CSSProperties,
DOMAttributes,
PropsWithChildren,
RefAttributes,
useContext,
useEffect,
useLayoutEffect,
Expand Down Expand Up @@ -185,18 +187,39 @@ export const Map = (props: PropsWithChildren<MapProps>) => {
}

return (
<div
<gmp-map
ref={mapRef}
data-testid={'map'}
style={className ? undefined : combinedStyle}
style={className ? undefined : (combinedStyle as CSSStyleDeclaration)}
className={className}
{...(id ? {id} : {})}>
{map ? (
<GoogleMapsContext.Provider value={contextValue}>
{children}
</GoogleMapsContext.Provider>
) : null}
</div>
</gmp-map>
);
};

Map.deckGLViewProps = true;

type CustomElement<T> = Partial<
T &
DOMAttributes<T> &
RefAttributes<T> & {
// for whatever reason, anything else doesn't work as children
// of a custom element, so we allow `any` here
// eslint-disable-next-line @typescript-eslint/no-explicit-any
children: any;
}
>;

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace JSX {
interface IntrinsicElements {
['gmp-map']: CustomElement<google.maps.MapElement>;
}
}
}
23 changes: 18 additions & 5 deletions src/components/map/use-map-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ import {
useTrackedCameraStateRef
} from './use-tracked-camera-state-ref';

function useMapElementDefined() {
const [isDefined, setIsDefined] = useState(false);

useEffect(() => {
customElements.whenDefined('gmp-map').then(() => setIsDefined(true));
}, []);

return isDefined;
}

/**
* The main hook takes care of creating map-instances and registering them in
* the api-provider context.
Expand All @@ -23,12 +33,13 @@ export function useMapInstance(
context: APIProviderContextValue
): readonly [
map: google.maps.Map | null,
containerRef: Ref<HTMLDivElement>,
containerRef: Ref<google.maps.MapElement>,
cameraStateRef: CameraStateRef
] {
const apiIsLoaded = useApiIsLoaded();
const mapElementDefined = useMapElementDefined();
const [map, setMap] = useState<google.maps.Map | null>(null);
const [container, containerRef] = useCallbackRef<HTMLDivElement>();
const [container, containerRef] = useCallbackRef<google.maps.MapElement>();

const cameraStateRef = useTrackedCameraStateRef(map);

Expand Down Expand Up @@ -63,11 +74,13 @@ export function useMapInstance(
// create the map instance and register it in the context
useEffect(
() => {
if (!container || !apiIsLoaded) return;
if (!container || !apiIsLoaded || !mapElementDefined) return;

const {addMapInstance, removeMapInstance} = context;
const mapId = props.mapId;
const newMap = new google.maps.Map(container, mapOptions);
// const newMap = new google.maps.Map(container, mapOptions);
const newMap = container.innerMap;
newMap.setOptions(mapOptions);

setMap(newMap);
addMapInstance(newMap, id);
Expand Down Expand Up @@ -105,7 +118,7 @@ export function useMapInstance(
// changes should be ignored
// - mapOptions has special hooks that take care of updating the options
// eslint-disable-next-line react-hooks/exhaustive-deps
[container, apiIsLoaded, id, props.mapId]
[container, apiIsLoaded, mapElementDefined, id, props.mapId]
);

return [map, containerRef, cameraStateRef] as const;
Expand Down
Loading