├── dev-docs └── RFCs │ └── .gitkeep ├── docs ├── upgrade-guide.md ├── .gitignore └── api-reference │ ├── hooks │ ├── use-api-loading-status.md │ ├── use-api-is-loaded.md │ └── use-maps-library.md │ └── components │ └── pin.md ├── website ├── .npmrc ├── .eslintignore ├── .gitignore ├── src │ ├── components │ │ ├── common.jsx │ │ ├── index.js │ │ ├── example │ │ │ └── doc-item-component.jsx │ │ └── home │ │ │ └── index.jsx │ ├── examples │ │ ├── map-3d.mdx │ │ ├── geometry.mdx │ │ ├── heatmap.mdx │ │ ├── basic-map.mdx │ │ ├── directions.mdx │ │ ├── static-map.mdx │ │ ├── places-ui-kit.mdx │ │ ├── routes-api.mdx │ │ ├── advanced-marker.mdx │ │ ├── deckgl-overlay.mdx │ │ ├── map-control.mdx │ │ ├── multiple-maps.mdx │ │ ├── change-map-styles.mdx │ │ ├── marker-clustering.mdx │ │ ├── markers-and-infowindows.mdx │ │ ├── custom-marker-clustering.mdx │ │ ├── advanced-marker-interaction.mdx │ │ ├── drawing.mdx │ │ ├── autocomplete.mdx │ │ ├── index.mdx │ │ └── extended-component-library.mdx │ ├── utils │ │ └── to-kebap-case.ts │ ├── docs-sidebar.js │ └── examples-sidebar.js ├── static │ ├── favicon.ico │ ├── images │ │ ├── visgl-logo.png │ │ ├── examples │ │ │ ├── drawing.jpg │ │ │ ├── heatmap.jpg │ │ │ ├── map-3d.jpg │ │ │ ├── basic-map.jpg │ │ │ ├── geometry.jpg │ │ │ ├── autocomplete.jpg │ │ │ ├── directions.jpg │ │ │ ├── map-control.jpg │ │ │ ├── routes-api.jpg │ │ │ ├── static-map.jpg │ │ │ ├── deckgl-overlay.jpg │ │ │ ├── multiple-maps.jpg │ │ │ ├── places-ui-kit.jpg │ │ │ ├── advanced-marker.jpg │ │ │ ├── change-map-styles.jpg │ │ │ ├── marker-clustering.jpg │ │ │ ├── custom-marker-clustering.jpg │ │ │ ├── markers-and-infowindows.jpg │ │ │ ├── advanced-marker-interaction.jpg │ │ │ └── extended-component-library.jpg │ │ ├── visgl-logo-dark.png │ │ └── visgl-logo-light.png │ └── scripts │ │ └── examples.js ├── babel.config.js ├── package.json └── ocular-docusaurus-plugin │ └── index.js ├── examples ├── map-3d │ ├── src │ │ ├── map-3d │ │ │ └── index.ts │ │ ├── minimap │ │ │ ├── index.ts │ │ │ ├── view-center-marker.css │ │ │ ├── camera-position-marker.css │ │ │ ├── view-center-marker.tsx │ │ │ ├── estimate-camera-position.ts │ │ │ └── camera-position-marker.tsx │ │ ├── style.css │ │ ├── utility-hooks.ts │ │ └── control-panel.tsx │ ├── types │ │ └── global.d.ts │ ├── package.json │ ├── vite.config.js │ ├── tsconfig.json │ └── index.html ├── advanced-marker │ ├── src │ │ ├── types │ │ │ ├── custom.d.ts │ │ │ └── types.ts │ │ ├── components │ │ │ └── control-panel.tsx │ │ └── style.css │ ├── data │ │ └── images │ │ │ ├── back.jpg │ │ │ ├── front.jpg │ │ │ └── bedroom.jpg │ ├── package.json │ ├── libs │ │ ├── format-currency.ts │ │ └── load-real-estate-listing.ts │ ├── vite.config.js │ ├── tsconfig.json │ ├── index.html │ └── icons │ │ └── real-estate-icon.tsx ├── remix │ ├── .gitignore │ ├── app │ │ ├── components │ │ │ ├── map │ │ │ │ ├── map.module.css │ │ │ │ ├── map-fallback.module.css │ │ │ │ ├── map-fallback.tsx │ │ │ │ └── map.client.tsx │ │ │ └── header │ │ │ │ ├── header.module.css │ │ │ │ └── header.tsx │ │ ├── public │ │ │ └── favicon.ico │ │ ├── global.d.ts │ │ ├── routes │ │ │ ├── about.tsx │ │ │ └── _index.tsx │ │ ├── styles │ │ │ └── about.module.css │ │ ├── index.css │ │ ├── entry.client.tsx │ │ └── root.tsx │ ├── postcss.config.js │ ├── vite.config.ts │ ├── tsconfig.json │ └── vite.config.local.ts ├── nextjs │ ├── .eslintrc.json │ ├── src │ │ ├── app │ │ │ ├── page.module.css │ │ │ ├── about │ │ │ │ ├── page.tsx │ │ │ │ └── page.module.css │ │ │ ├── layout.module.css │ │ │ ├── globals.css │ │ │ ├── components │ │ │ │ ├── header.module.css │ │ │ │ └── header.tsx │ │ │ ├── page.tsx │ │ │ └── layout.tsx │ │ └── global.d.ts │ ├── .example.env │ ├── next.config.ts │ ├── package.json │ ├── tsconfig.sandbox.json │ ├── tsconfig.local.json │ └── .gitignore ├── geometry │ ├── src │ │ └── components │ │ │ └── index.ts │ ├── package.json │ ├── vite.config.js │ └── index.html ├── directions │ ├── src │ │ └── control-panel.css │ ├── package.json │ └── vite.config.js ├── marker-clustering │ ├── src │ │ ├── style.css │ │ └── tree-marker.tsx │ ├── package.json │ ├── vite.config.js │ └── index.html ├── global.d.ts ├── autocomplete │ ├── src │ │ ├── control-panel.css │ │ └── autocomplete-result.tsx │ ├── package.json │ ├── vite.config.js │ └── index.html ├── routes-api │ ├── src │ │ ├── app.css │ │ └── control-panel.tsx │ ├── package.json │ ├── vite.config.js │ ├── index.html │ └── README.md ├── places-ui-kit-3d │ ├── types │ │ └── global.d.ts │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── dropdown │ │ │ │ ├── dropdown.css │ │ │ │ └── dropdown.tsx │ │ │ ├── popover │ │ │ │ ├── popover.css │ │ │ │ └── popover.tsx │ │ │ ├── overlay │ │ │ │ └── overlay.css │ │ │ └── reference-marker │ │ │ │ └── reference-marker.tsx │ │ └── utility-hooks.ts │ ├── vite.config.js │ ├── tsconfig.json │ ├── index.html │ └── README.md ├── advanced-marker-interaction │ ├── types │ │ └── global.d.ts │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── style.css │ │ └── data.ts │ ├── vite.config.js │ ├── index.html │ └── README.md ├── _template │ ├── package.json │ ├── vite.config.js │ ├── index.html │ ├── src │ │ ├── control-panel.tsx │ │ └── app.tsx │ └── README.md ├── basic-map │ ├── package.json │ ├── vite.config.js │ ├── src │ │ ├── control-panel.tsx │ │ └── app.tsx │ ├── index.html │ └── README.md ├── drawing │ ├── package.json │ ├── vite.config.js │ ├── src │ │ ├── app.tsx │ │ ├── drawing-example.tsx │ │ └── control-panel.tsx │ └── index.html ├── map-control │ ├── package.json │ ├── vite.config.js │ ├── index.html │ ├── src │ │ └── custom-zoom-control.tsx │ └── README.md ├── multiple-maps │ ├── package.json │ ├── vite.config.js │ ├── index.html │ ├── src │ │ └── control-panel.tsx │ └── README.md ├── places-ui-kit │ ├── package.json │ ├── vite.config.js │ └── index.html ├── static-map │ ├── package.json │ ├── vite.config.js │ ├── src │ │ ├── static-map-1.tsx │ │ ├── control-panel.tsx │ │ ├── static-map-3.tsx │ │ ├── app.tsx │ │ └── static-map-2.tsx │ ├── index.html │ └── README.md ├── change-map-styles │ ├── package.json │ ├── vite.config.js │ ├── index.html │ └── README.md ├── homepage-header │ ├── package.json │ ├── vite.config.js │ ├── index.html │ └── README.md ├── markers-and-infowindows │ ├── package.json │ ├── vite.config.js │ ├── src │ │ ├── moving-marker.tsx │ │ ├── control-panel.tsx │ │ └── marker-with-infowindow.tsx │ ├── index.html │ └── README.md ├── heatmap │ ├── package.json │ ├── vite.config.js │ ├── src │ │ └── earthquakes.ts │ ├── index.html │ └── README.md ├── react-wrapper-migration │ ├── package.json │ ├── vite.config.js │ ├── index.html │ ├── src │ │ └── app.tsx │ └── README.md ├── deckgl-overlay │ ├── package.json │ ├── vite.config.js │ ├── index.html │ └── src │ │ ├── control-panel.tsx │ │ └── deckgl-overlay.ts ├── custom-marker-clustering │ ├── src │ │ ├── castles.ts │ │ └── components │ │ │ ├── feature-marker.tsx │ │ │ └── features-cluster-marker.tsx │ ├── package.json │ ├── vite.config.js │ ├── index.html │ ├── data │ │ └── README.md │ └── README.md ├── extended-component-library │ ├── vite.config.js │ ├── package.json │ ├── src │ │ ├── app.css │ │ └── control-panel.tsx │ └── index.html ├── vite.config.local.js └── README.md ├── .npmignore ├── src ├── server │ └── index.ts ├── version.ts ├── custom-elements-types │ ├── index.d.ts │ ├── utils.d.ts │ ├── airQuality.d.ts │ └── routes.d.ts ├── hooks │ ├── use-force-update.ts │ ├── use-callback-ref.ts │ ├── use-api-loading-status.ts │ ├── use-api-is-loaded.ts │ ├── use-deep-compare-effect.ts │ ├── use-memoized.ts │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── use-map.test.tsx.snap │ │ └── __utils__ │ │ │ ├── wait-for-spy.ts │ │ │ └── wait-for-mock-instance.ts │ ├── use-custom-compare-efffect.ts │ ├── use-previous.ts │ ├── use-prop-binding.ts │ ├── use-dom-event-listener.ts │ ├── use-maps-event-listener.ts │ └── use-map.ts ├── components │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ ├── pin.test.tsx.snap │ │ │ └── advanced-marker.test.tsx.snap │ │ └── __utils__ │ │ │ ├── wait-for-spy.ts │ │ │ └── wait-for-mock-instance.ts │ ├── static-map.tsx │ └── map │ │ ├── auth-failure-message.tsx │ │ └── use-deckgl-camera-update.ts ├── libraries │ ├── errors.ts │ ├── api-loading-status.ts │ ├── __tests__ │ │ └── __snapshots__ │ │ │ └── google-maps-api-loader.test.ts.snap │ ├── assert-not-null.ts │ ├── version-utils.ts │ ├── __mocks__ │ │ └── google-maps-api-loader.ts │ ├── lat-lng-utils.ts │ ├── limit-tilt-range.ts │ ├── create-static-maps-url │ │ ├── helpers.ts │ │ └── types.ts │ └── global-style-manager.ts └── index.ts ├── .prettierignore ├── .gitignore ├── tsconfig.build.json ├── tsconfig.test.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── feature-request.yml ├── workflows │ ├── test.yml │ ├── test-website.yml │ └── release-please.yml ├── dependabot.yml └── SECURITY.md ├── .prettierrc ├── jest.config.js ├── CODE_OF_CONDUCT.md ├── scripts ├── install-examples.sh ├── build-examples.sh └── update-examples.sh ├── .ocularrc.js ├── tsconfig.json └── LICENSE.md /dev-docs/RFCs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/upgrade-guide.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | -------------------------------------------------------------------------------- /website/.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps = true 2 | -------------------------------------------------------------------------------- /website/.eslintignore: -------------------------------------------------------------------------------- 1 | ../dist 2 | ../examples 3 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | api-reference/web-mercator-viewport.md 2 | -------------------------------------------------------------------------------- /examples/map-3d/src/map-3d/index.ts: -------------------------------------------------------------------------------- 1 | export * from './map-3d'; 2 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/index.ts: -------------------------------------------------------------------------------- 1 | export * from './minimap'; 2 | -------------------------------------------------------------------------------- /examples/advanced-marker/src/types/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.jpg'; 2 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.cache 3 | /.docusaurus 4 | /build 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | dist 4 | docs 5 | src 6 | tsconfig.json 7 | -------------------------------------------------------------------------------- /examples/remix/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | .env 6 | -------------------------------------------------------------------------------- /examples/nextjs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/page.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /examples/remix/app/components/map/map.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /examples/remix/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /website/src/components/common.jsx: -------------------------------------------------------------------------------- 1 | export const isMobile = props => `@media screen and (max-width: 480px)`; 2 | -------------------------------------------------------------------------------- /website/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/favicon.ico -------------------------------------------------------------------------------- /website/src/examples/map-3d.mdx: -------------------------------------------------------------------------------- 1 | # 3D Maps 2 | 3 | import App from 'website-examples/map-3d/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/nextjs/src/global.d.ts: -------------------------------------------------------------------------------- 1 | // global.d.ts 2 | declare module globalThis { 3 | var GOOGLE_MAPS_API_KEY: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/server/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export * from '../components/static-map'; 4 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | // This file is automatically updated by the build process. 2 | export const VERSION = '__PACKAGE_VERSION__'; 3 | -------------------------------------------------------------------------------- /website/src/examples/geometry.mdx: -------------------------------------------------------------------------------- 1 | # Geometry 2 | 3 | import App from 'website-examples/geometry/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/heatmap.mdx: -------------------------------------------------------------------------------- 1 | # Heatmap 2 | 3 | import App from 'website-examples/heatmap/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .npmignore 4 | CHANGELOG.md 5 | examples/nextjs/.next 6 | examples/nextjs/next.lock 7 | -------------------------------------------------------------------------------- /examples/remix/app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/examples/remix/app/public/favicon.ico -------------------------------------------------------------------------------- /website/src/examples/basic-map.mdx: -------------------------------------------------------------------------------- 1 | # Basic Map 2 | 3 | import App from 'website-examples/basic-map/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/directions.mdx: -------------------------------------------------------------------------------- 1 | # Directions 2 | 3 | import App from 'website-examples/directions/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/static-map.mdx: -------------------------------------------------------------------------------- 1 | # Static Map 2 | 3 | import App from 'website-examples/static-map/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/visgl-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/visgl-logo.png -------------------------------------------------------------------------------- /examples/geometry/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './circle'; 2 | export * from './polygon'; 3 | export * from './polyline'; 4 | -------------------------------------------------------------------------------- /examples/remix/app/global.d.ts: -------------------------------------------------------------------------------- 1 | // global.d.ts 2 | export declare namespace globalThis { 3 | const GOOGLE_MAPS_API_KEY: string; 4 | } 5 | -------------------------------------------------------------------------------- /examples/directions/src/control-panel.css: -------------------------------------------------------------------------------- 1 | .control-panel { 2 | .note { 3 | font-size: 0.8em; 4 | margin: 1em 0; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /website/src/examples/places-ui-kit.mdx: -------------------------------------------------------------------------------- 1 | # Places UI Kit 2 | 3 | import App from 'website-examples/places-ui-kit/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/routes-api.mdx: -------------------------------------------------------------------------------- 1 | # Routes API Rendering 2 | 3 | import App from 'website-examples/routes-api/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/examples/drawing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/drawing.jpg -------------------------------------------------------------------------------- /website/static/images/examples/heatmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/heatmap.jpg -------------------------------------------------------------------------------- /website/static/images/examples/map-3d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/map-3d.jpg -------------------------------------------------------------------------------- /website/static/images/visgl-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/visgl-logo-dark.png -------------------------------------------------------------------------------- /website/static/images/visgl-logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/visgl-logo-light.png -------------------------------------------------------------------------------- /website/src/examples/advanced-marker.mdx: -------------------------------------------------------------------------------- 1 | # Advanced Marker 2 | 3 | import App from 'website-examples/advanced-marker/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/deckgl-overlay.mdx: -------------------------------------------------------------------------------- 1 | # Deck.gl Overlay 2 | 3 | import App from 'website-examples/deckgl-overlay/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/map-control.mdx: -------------------------------------------------------------------------------- 1 | # Custom Map Controls 2 | 3 | import App from 'website-examples/map-control/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/multiple-maps.mdx: -------------------------------------------------------------------------------- 1 | # Synchronized Maps 2 | 3 | import App from 'website-examples/multiple-maps/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/examples/basic-map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/basic-map.jpg -------------------------------------------------------------------------------- /website/static/images/examples/geometry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/geometry.jpg -------------------------------------------------------------------------------- /examples/advanced-marker/data/images/back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/examples/advanced-marker/data/images/back.jpg -------------------------------------------------------------------------------- /examples/advanced-marker/data/images/front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/examples/advanced-marker/data/images/front.jpg -------------------------------------------------------------------------------- /website/src/components/index.js: -------------------------------------------------------------------------------- 1 | export {default as ExamplesIndex} from './example/examples-index'; 2 | 3 | export {default as Home} from './home'; 4 | -------------------------------------------------------------------------------- /website/static/images/examples/autocomplete.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/autocomplete.jpg -------------------------------------------------------------------------------- /website/static/images/examples/directions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/directions.jpg -------------------------------------------------------------------------------- /website/static/images/examples/map-control.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/map-control.jpg -------------------------------------------------------------------------------- /website/static/images/examples/routes-api.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/routes-api.jpg -------------------------------------------------------------------------------- /website/static/images/examples/static-map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/static-map.jpg -------------------------------------------------------------------------------- /examples/advanced-marker/data/images/bedroom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/examples/advanced-marker/data/images/bedroom.jpg -------------------------------------------------------------------------------- /website/src/examples/change-map-styles.mdx: -------------------------------------------------------------------------------- 1 | # Change Map Styles 2 | 3 | import App from 'website-examples/change-map-styles/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/marker-clustering.mdx: -------------------------------------------------------------------------------- 1 | # Marker Clustering 2 | 3 | import App from 'website-examples/marker-clustering/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/examples/deckgl-overlay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/deckgl-overlay.jpg -------------------------------------------------------------------------------- /website/static/images/examples/multiple-maps.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/multiple-maps.jpg -------------------------------------------------------------------------------- /website/static/images/examples/places-ui-kit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/places-ui-kit.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /examples/**/package-lock.json 4 | /examples/**/node_modules 5 | /examples/**/dist 6 | /examples/**/.env* 7 | .vscode/ 8 | -------------------------------------------------------------------------------- /website/static/images/examples/advanced-marker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/advanced-marker.jpg -------------------------------------------------------------------------------- /website/static/images/examples/change-map-styles.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/change-map-styles.jpg -------------------------------------------------------------------------------- /website/static/images/examples/marker-clustering.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/marker-clustering.jpg -------------------------------------------------------------------------------- /website/src/examples/markers-and-infowindows.mdx: -------------------------------------------------------------------------------- 1 | # Markers and Infowindows 2 | 3 | import App from 'website-examples/markers-and-infowindows/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["./src/**/__*__", "./examples"], 4 | "compilerOptions": { 5 | "noEmit": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /website/src/examples/custom-marker-clustering.mdx: -------------------------------------------------------------------------------- 1 | # Custom Marker Clustering 2 | 3 | import App from 'website-examples/custom-marker-clustering/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/examples/custom-marker-clustering.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/custom-marker-clustering.jpg -------------------------------------------------------------------------------- /website/static/images/examples/markers-and-infowindows.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/markers-and-infowindows.jpg -------------------------------------------------------------------------------- /examples/nextjs/.example.env: -------------------------------------------------------------------------------- 1 | # Duplicate this file and rename it to ".env" and enter your Google Maps API key here 2 | NEXT_PUBLIC_GOOGLE_MAPS_API_KEY= 3 | -------------------------------------------------------------------------------- /website/static/images/examples/advanced-marker-interaction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/advanced-marker-interaction.jpg -------------------------------------------------------------------------------- /website/static/images/examples/extended-component-library.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visgl/react-google-maps/HEAD/website/static/images/examples/extended-component-library.jpg -------------------------------------------------------------------------------- /examples/marker-clustering/src/style.css: -------------------------------------------------------------------------------- 1 | .marker-clustering-tree { 2 | font-size: 2rem; 3 | } 4 | 5 | .marker-clustering-control-panel .attribution { 6 | font-size: 0.75em; 7 | } 8 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/about/page.tsx: -------------------------------------------------------------------------------- 1 | import styles from './page.module.css'; 2 | 3 | export default function About() { 4 | return
About
; 5 | } 6 | -------------------------------------------------------------------------------- /website/src/examples/advanced-marker-interaction.mdx: -------------------------------------------------------------------------------- 1 | # Advanced Marker interaction 2 | 3 | import App from 'website-examples/advanced-marker-interaction/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/nextjs/next.config.ts: -------------------------------------------------------------------------------- 1 | import type {NextConfig} from 'next'; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/layout.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100vw; 3 | height: 100vh; 4 | display: grid; 5 | grid-template-columns: 1fr; 6 | grid-template-rows: 60px 1fr; 7 | } 8 | -------------------------------------------------------------------------------- /examples/remix/app/components/map/map-fallback.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | } 8 | -------------------------------------------------------------------------------- /examples/remix/app/routes/about.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/about.module.css'; 2 | 3 | export default function About() { 4 | return
About
; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["./src/**/*"], 4 | "exclude": ["./examples/**/*"], 5 | "compilerOptions": { 6 | "noEmit": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | max-width: 100vw; 4 | overflow-x: hidden; 5 | } 6 | 7 | * { 8 | box-sizing: border-box; 9 | padding: 0; 10 | margin: 0; 11 | } 12 | -------------------------------------------------------------------------------- /examples/global.d.ts: -------------------------------------------------------------------------------- 1 | export declare global { 2 | // const or let does not work in this case, it has to be var 3 | // eslint-disable-next-line no-var 4 | var GOOGLE_MAPS_API_KEY: string | undefined; 5 | } 6 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/about/page.module.css: -------------------------------------------------------------------------------- 1 | .page { 2 | width: 100%; 3 | height: 100%; 4 | background: aliceblue; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | -------------------------------------------------------------------------------- /examples/remix/app/styles/about.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100%; 4 | background: aliceblue; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | -------------------------------------------------------------------------------- /src/custom-elements-types/index.d.ts: -------------------------------------------------------------------------------- 1 | export type * from './marker'; 2 | export type * from './maps3d'; 3 | export type * from './places'; 4 | export type * from './airQuality'; 5 | export type * from './routes'; 6 | -------------------------------------------------------------------------------- /src/hooks/use-force-update.ts: -------------------------------------------------------------------------------- 1 | import {useReducer} from 'react'; 2 | 3 | export function useForceUpdate(): () => void { 4 | const [, forceUpdate] = useReducer(x => x + 1, 0); 5 | 6 | return forceUpdate; 7 | } 8 | -------------------------------------------------------------------------------- /website/src/examples/drawing.mdx: -------------------------------------------------------------------------------- 1 | # Drawing 2 | 3 | import BrowserOnly from '@docusaurus/BrowserOnly'; 4 | import App from 'website-examples/drawing/src/app'; 5 | 6 | {() => } 7 | -------------------------------------------------------------------------------- /examples/remix/app/components/map/map-fallback.tsx: -------------------------------------------------------------------------------- 1 | import styles from './map-fallback.module.css'; 2 | 3 | export default function MyMapFallback() { 4 | return
Map loading...
; 5 | } 6 | -------------------------------------------------------------------------------- /website/src/examples/autocomplete.mdx: -------------------------------------------------------------------------------- 1 | # Autocomplete 2 | 3 | import BrowserOnly from '@docusaurus/BrowserOnly'; 4 | import App from 'website-examples/autocomplete/src/app'; 5 | 6 | {() => } 7 | -------------------------------------------------------------------------------- /src/custom-elements-types/utils.d.ts: -------------------------------------------------------------------------------- 1 | import type React from 'react'; 2 | 3 | export type CustomElement< 4 | P, 5 | E extends HTMLElement = HTMLElement 6 | > = React.DetailedHTMLProps, E> & P; 7 | 8 | export {}; 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: I have a question / I need help 4 | url: https://github.com/visgl/react-google-maps/discussions 5 | about: Ask generic questions or request help here 6 | -------------------------------------------------------------------------------- /examples/autocomplete/src/control-panel.css: -------------------------------------------------------------------------------- 1 | .control-panel { 2 | select { 3 | max-width: 100%; 4 | text-overflow: ellipsis; 5 | } 6 | 7 | .implementation-details { 8 | font-size: 0.8em; 9 | margin: 1em 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": false, 9 | "bracketSameLine": true, 10 | "arrowParens": "avoid" 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [''], 3 | testEnvironment: 'jsdom', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist/', '/__utils__/'], 5 | transform: { 6 | '^.+.tsx?$': ['ts-jest', {tsconfig: 'tsconfig.test.json'}] 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /website/src/examples/index.mdx: -------------------------------------------------------------------------------- 1 | import {ExamplesIndex} from '../components'; 2 | import {toKebapCase} from '../utils/to-kebap-case'; 3 | 4 | 6 | `/images/examples/${item.docId || toKebapCase(item.label)}.jpg` 7 | } 8 | /> 9 | -------------------------------------------------------------------------------- /examples/routes-api/src/app.css: -------------------------------------------------------------------------------- 1 | .route-api-example { 2 | width: 100%; 3 | height: 100%; 4 | 5 | .route-step-marker { 6 | width: 8px; 7 | height: 8px; 8 | border: 1px solid black; 9 | border-radius: 50%; 10 | background: #777; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /website/static/scripts/examples.js: -------------------------------------------------------------------------------- 1 | // For the codesandbox examples, the 'process.env.GOOGLE_MAPS_API_KEY' 2 | // gets replaced in the deploy task in the github action 3 | // to contain the valid key for the examples 4 | globalThis.GOOGLE_MAPS_API_KEY = process.env.GOOGLE_MAPS_API_KEY; 5 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/pin.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`pin view logs an error when used outside of AdvancedMarker 1`] = ` 4 | [ 5 | "The component can only be used inside .", 6 | ] 7 | `; 8 | -------------------------------------------------------------------------------- /website/src/utils/to-kebap-case.ts: -------------------------------------------------------------------------------- 1 | export function toKebapCase(s: string) { 2 | const matches = s.match( 3 | /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g 4 | ); 5 | 6 | if (!matches) return s; 7 | 8 | return matches.join('-').toLowerCase(); 9 | } 10 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | plugins: [ 4 | 'version-inline', 5 | // Ensure consistently hashed component classNames between environments (a must for SSR) 6 | 'styled-components' 7 | ] 8 | }; 9 | -------------------------------------------------------------------------------- /src/hooks/use-callback-ref.ts: -------------------------------------------------------------------------------- 1 | import {Ref, useCallback, useState} from 'react'; 2 | 3 | export function useCallbackRef() { 4 | const [el, setEl] = useState(null); 5 | const ref = useCallback((value: T) => setEl(value), [setEl]); 6 | 7 | return [el, ref as Ref] as const; 8 | } 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | vis.gl is an [OpenJS Foundation](https://openjsf.org/) project. Please be mindful of and adhere to the OpenJS Foundation's [Code of Conduct](https://github.com/openjs-foundation/cross-project-council/blob/main/CODE_OF_CONDUCT.md) when contributing to deck.gl. 4 | -------------------------------------------------------------------------------- /src/libraries/errors.ts: -------------------------------------------------------------------------------- 1 | const shownMessages = new Set(); 2 | 3 | export function logErrorOnce(...args: Parameters) { 4 | const key = JSON.stringify(args); 5 | 6 | if (!shownMessages.has(key)) { 7 | shownMessages.add(key); 8 | 9 | console.error(...args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/view-center-marker.css: -------------------------------------------------------------------------------- 1 | .view-center-marker { 2 | width: 0; 3 | height: 0; 4 | } 5 | 6 | .view-center-marker .circle { 7 | width: 10px; 8 | height: 10px; 9 | background-color: blue; 10 | border: 1px solid black; 11 | border-radius: 50%; 12 | translate: -50% -50%; 13 | } 14 | -------------------------------------------------------------------------------- /examples/map-3d/types/global.d.ts: -------------------------------------------------------------------------------- 1 | export declare global { 2 | // const or let does not work in this case, it has to be var 3 | // eslint-disable-next-line no-var 4 | var GOOGLE_MAPS_API_KEY: string | undefined; 5 | // eslint-disable-next-line no-var, @typescript-eslint/no-explicit-any 6 | var process: any; 7 | } 8 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/types/global.d.ts: -------------------------------------------------------------------------------- 1 | export declare global { 2 | // const or let does not work in this case, it has to be var 3 | // eslint-disable-next-line no-var 4 | var GOOGLE_MAPS_API_KEY: string | undefined; 5 | // eslint-disable-next-line no-var, @typescript-eslint/no-explicit-any 6 | var process: any; 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/api-loading-status.ts: -------------------------------------------------------------------------------- 1 | export const APILoadingStatus = { 2 | NOT_LOADED: 'NOT_LOADED', 3 | LOADING: 'LOADING', 4 | LOADED: 'LOADED', 5 | FAILED: 'FAILED', 6 | AUTH_FAILURE: 'AUTH_FAILURE' 7 | } as const; 8 | export type APILoadingStatus = 9 | (typeof APILoadingStatus)[keyof typeof APILoadingStatus]; 10 | -------------------------------------------------------------------------------- /website/src/examples/extended-component-library.mdx: -------------------------------------------------------------------------------- 1 | # Extended Component Library 2 | 3 | import BrowserOnly from '@docusaurus/BrowserOnly'; 4 | 5 | 6 | {() => { 7 | const App = require('website-examples/extended-component-library/src/app').default; 8 | 9 | return ; 10 | }} 11 | 12 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/types/global.d.ts: -------------------------------------------------------------------------------- 1 | export declare global { 2 | // const or let does not work in this case, it has to be var 3 | // eslint-disable-next-line no-var 4 | var GOOGLE_MAPS_API_KEY: string | undefined; 5 | // eslint-disable-next-line no-var, @typescript-eslint/no-explicit-any 6 | var process: any; 7 | } 8 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@vis.gl/react-google-maps": ["../../src"] 7 | } 8 | }, 9 | "include": ["src/**/*", "../../src/**/*", "./types/**/*"], 10 | "exclude": ["node_modules", "dist"] 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/use-api-loading-status.ts: -------------------------------------------------------------------------------- 1 | import {useContext} from 'react'; 2 | import {APIProviderContext} from '../components/api-provider'; 3 | import {APILoadingStatus} from '../libraries/api-loading-status'; 4 | 5 | export function useApiLoadingStatus(): APILoadingStatus { 6 | return useContext(APIProviderContext)?.status || APILoadingStatus.NOT_LOADED; 7 | } 8 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/components/header.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .header { 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | } 11 | 12 | .nav ul { 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | gap: 16px; 17 | list-style: none; 18 | } 19 | -------------------------------------------------------------------------------- /examples/remix/app/routes/_index.tsx: -------------------------------------------------------------------------------- 1 | import {ClientOnly} from 'remix-utils/client-only'; 2 | 3 | import MyMap from '../components/map/map.client'; 4 | import MyMapFallback from '../components/map/map-fallback'; 5 | 6 | export default function Index() { 7 | return ( 8 | }>{() => } 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /examples/remix/app/components/header/header.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .header { 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | } 11 | 12 | .nav ul { 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | gap: 16px; 17 | list-style: none; 18 | } 19 | -------------------------------------------------------------------------------- /examples/_template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/basic-map/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/drawing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/geometry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/directions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/camera-position-marker.css: -------------------------------------------------------------------------------- 1 | .camera-position-marker svg { 2 | --camera-heading: 0; 3 | 4 | transform-origin: 50% 0; 5 | translate: -50% -50%; 6 | rotate: calc(var(--camera-heading) * 1deg); 7 | } 8 | 9 | .camera-position-marker path { 10 | fill: red; 11 | stroke: black; 12 | stroke-width: 2; 13 | vector-effect: non-scaling-stroke; 14 | } 15 | -------------------------------------------------------------------------------- /examples/map-control/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/multiple-maps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/places-ui-kit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/routes-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/static-map/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/change-map-styles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/homepage-header/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^6.0.11" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/remix/app/index.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html, 10 | body { 11 | max-width: 100vw; 12 | overflow-x: hidden; 13 | } 14 | 15 | .container { 16 | width: 100vw; 17 | height: 100vh; 18 | display: grid; 19 | grid-template-columns: 1fr; 20 | grid-template-rows: 60px 1fr; 21 | } 22 | -------------------------------------------------------------------------------- /src/hooks/use-api-is-loaded.ts: -------------------------------------------------------------------------------- 1 | import {useApiLoadingStatus} from './use-api-loading-status'; 2 | import {APILoadingStatus} from '../libraries/api-loading-status'; 3 | /** 4 | * Hook to check if the Maps JavaScript API is loaded 5 | */ 6 | export function useApiIsLoaded(): boolean { 7 | const status = useApiLoadingStatus(); 8 | 9 | return status === APILoadingStatus.LOADED; 10 | } 11 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /scripts/install-examples.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | examplesRoot="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"/examples 4 | 5 | for d in $examplesRoot/*/; do 6 | # Skip the template directory 7 | if [ "$(basename $d)" = "_template" ]; then 8 | continue 9 | fi 10 | 11 | echo ">>> installing example '$(basename $d)'" 12 | cd $d 13 | npm i --silent 14 | done 15 | 16 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "vite": "^7.1.7" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/hooks/use-deep-compare-effect.ts: -------------------------------------------------------------------------------- 1 | import {DependencyList, EffectCallback} from 'react'; 2 | import {useCustomCompareEffect} from './use-custom-compare-efffect'; 3 | import isDeepEqual from 'fast-deep-equal'; 4 | 5 | export function useDeepCompareEffect( 6 | effect: EffectCallback, 7 | dependencies: DependencyList 8 | ) { 9 | useCustomCompareEffect(effect, dependencies, isDeepEqual); 10 | } 11 | -------------------------------------------------------------------------------- /examples/advanced-marker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "classnames": "^2.5.1", 6 | "react": "^19.0.0", 7 | "react-dom": "^19.0.0", 8 | "vite": "^7.1.7" 9 | }, 10 | "scripts": { 11 | "start": "vite", 12 | "start-local": "vite --config ../vite.config.local.js", 13 | "build": "vite build" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/autocomplete/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^19.0.0", 6 | "react-dom": "^19.0.0", 7 | "react-widgets": "^5.8.4", 8 | "vite": "^7.1.7" 9 | }, 10 | "scripts": { 11 | "start": "vite", 12 | "start-local": "vite --config ../vite.config.local.js", 13 | "build": "vite build" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/heatmap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@types/geojson": "^7946.0.14", 5 | "@vis.gl/react-google-maps": "latest", 6 | "react": "^19.0.0", 7 | "react-dom": "^19.0.0", 8 | "vite": "^7.1.7" 9 | }, 10 | "scripts": { 11 | "start": "vite", 12 | "start-local": "vite --config ../vite.config.local.js", 13 | "build": "vite build" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/hooks/use-memoized.ts: -------------------------------------------------------------------------------- 1 | import {useMemo} from 'react'; 2 | import {usePrevious} from './use-previous'; 3 | 4 | export function useMemoized(value: T, isEqual: (a: T, b: T) => boolean): T { 5 | const previous = usePrevious(value); 6 | return useMemo(() => { 7 | if (previous && isEqual(previous, value)) { 8 | return previous; 9 | } 10 | return value; 11 | }, [value, previous, isEqual]); 12 | } 13 | -------------------------------------------------------------------------------- /examples/marker-clustering/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@googlemaps/markerclusterer": "^2.5.1", 5 | "@vis.gl/react-google-maps": "latest", 6 | "react": "^19.0.0", 7 | "react-dom": "^19.0.0", 8 | "vite": "^7.1.7" 9 | }, 10 | "scripts": { 11 | "start": "vite", 12 | "start-local": "vite --config ../vite.config.local.js", 13 | "build": "vite build" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/hooks/__tests__/__snapshots__/use-map.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`it should log an error when used outside the APIProvider 1`] = ` 4 | [ 5 | [ 6 | "useMap(): failed to retrieve APIProviderContext. Make sure that the component exists and that the component you are calling \`useMap()\` from is a sibling of the .", 7 | ], 8 | ] 9 | `; 10 | -------------------------------------------------------------------------------- /examples/map-3d/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@turf/turf": "^7.1.0", 5 | "@vis.gl/react-google-maps": "latest", 6 | "react": "^19.0.0", 7 | "react-dom": "^19.0.0", 8 | "typescript": "^5.4.5", 9 | "vite": "^7.1.7" 10 | }, 11 | "scripts": { 12 | "start": "vite", 13 | "start-local": "vite --config ../vite.config.local.js", 14 | "build": "vite build" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/react-wrapper-migration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@googlemaps/react-wrapper": "^1.1.42", 5 | "@vis.gl/react-google-maps": "latest", 6 | "react": "^19.0.0", 7 | "react-dom": "^19.0.0", 8 | "vite": "^7.1.7" 9 | }, 10 | "scripts": { 11 | "start": "vite", 12 | "start-local": "vite --config ../vite.config.local.js", 13 | "build": "vite build" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@deck.gl/google-maps": "^9.1.0", 5 | "@vis.gl/react-google-maps": "latest", 6 | "deck.gl": "^9.1.0", 7 | "react": "^19.0.0", 8 | "react-dom": "^19.0.0", 9 | "vite": "^7.1.7" 10 | }, 11 | "scripts": { 12 | "start": "vite", 13 | "start-local": "vite --config ../vite.config.local.js", 14 | "build": "vite build" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/__tests__/__utils__/wait-for-spy.ts: -------------------------------------------------------------------------------- 1 | import {waitFor} from '@testing-library/react'; 2 | 3 | /** 4 | * Waits for a specified spy/mock-function to be called and returns the 5 | * arguments passed to the last call. 6 | * @param spy 7 | */ 8 | export async function waitForSpy( 9 | spy: T 10 | ): Promise> { 11 | await waitFor(() => expect(spy).toHaveBeenCalled()); 12 | 13 | return spy.mock.lastCall; 14 | } 15 | -------------------------------------------------------------------------------- /src/hooks/use-custom-compare-efffect.ts: -------------------------------------------------------------------------------- 1 | import {DependencyList, EffectCallback, useEffect} from 'react'; 2 | import {useMemoized} from './use-memoized'; 3 | 4 | export function useCustomCompareEffect( 5 | effect: EffectCallback, 6 | dependencies: T, 7 | isEqual: (a: T, b: T) => boolean 8 | ) { 9 | // eslint-disable-next-line react-hooks/exhaustive-deps 10 | useEffect(effect, [useMemoized(dependencies, isEqual)]); 11 | } 12 | -------------------------------------------------------------------------------- /src/libraries/__tests__/__snapshots__/google-maps-api-loader.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`GoogleMapsApiLoader logs a warning when called multiple times with different parameters 1`] = ` 4 | [ 5 | [ 6 | "[google-maps-api-loader] The maps API has already been loaded with different parameters and will not be loaded again. Refresh the page for new values to have effect.", 7 | ], 8 | ] 9 | `; 10 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/src/components/dropdown/dropdown.css: -------------------------------------------------------------------------------- 1 | .dropdown { 2 | background: white; 3 | position: relative; 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | border-top: 1px solid #e8eaed; 8 | padding: 18px 16px; 9 | 10 | font-size: 14px; 11 | font-weight: 500; 12 | line-height: 20px; 13 | font-family: 'Roboto'; 14 | color: #5f6368; 15 | 16 | label { 17 | white-space: nowrap; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/hooks/use-previous.ts: -------------------------------------------------------------------------------- 1 | import {useEffect, useRef} from 'react'; 2 | 3 | /** 4 | * A hook to store the previous value of a variable. 5 | * @param value The value to store 6 | * @returns The previous value 7 | */ 8 | export function usePrevious(value: T): T | undefined { 9 | const ref = useRef(undefined); 10 | useEffect(() => { 11 | ref.current = value; 12 | }); 13 | // eslint-disable-next-line react-hooks/refs 14 | return ref.current; 15 | } 16 | -------------------------------------------------------------------------------- /src/libraries/assert-not-null.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A typescript assertion function used in cases where typescript has to be 3 | * convinced that the object in question can not be null. 4 | * 5 | * @param value 6 | * @param message 7 | */ 8 | export function assertNotNull( 9 | value: TValue, 10 | message = 'assertion failed' 11 | ): asserts value is NonNullable { 12 | if (value === null || value === undefined) { 13 | throw Error(message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/libraries/version-utils.ts: -------------------------------------------------------------------------------- 1 | export function isVersionGreaterEqual( 2 | major: number, 3 | minor: number 4 | ): boolean | undefined { 5 | if (!google?.maps?.version) return undefined; 6 | 7 | const version = google.maps.version.split('.'); 8 | 9 | const currentMajor = parseInt(version[0], 10); 10 | const currentMinor = parseInt(version[1], 10); 11 | 12 | return ( 13 | currentMajor > major || (currentMajor === major && currentMinor >= minor) 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /website/src/docs-sidebar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | const sidebars = { 12 | docsSidebar: require('../../docs/table-of-contents.json') 13 | }; 14 | 15 | module.exports = sidebars; 16 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@v4 9 | 10 | - name: Setup Node 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version: 20 14 | cache: npm 15 | 16 | - name: Install Dependencies 17 | run: npm ci 18 | 19 | - name: Run Unit Tests 20 | run: npm test 21 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/view-center-marker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {AdvancedMarker} from '@vis.gl/react-google-maps'; 3 | 4 | import './view-center-marker.css'; 5 | 6 | type ViewCenterMarkerProps = {position: google.maps.LatLngAltitudeLiteral}; 7 | export const ViewCenterMarker = ({position}: ViewCenterMarkerProps) => ( 8 | 9 |
10 | 11 | ); 12 | -------------------------------------------------------------------------------- /scripts/build-examples.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | examplesRoot="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"/examples 4 | 5 | for d in $examplesRoot/*/; do 6 | # Skip the template directory 7 | if [ "$(basename $d)" = "_template" ]; then 8 | continue 9 | fi 10 | 11 | echo ">>> building example '$(basename $d)'" 12 | cd $d 13 | npm run build 14 | 15 | if [ ! $? ] ; then 16 | echo "\n\nBUILD FAILED!\n\n"; 17 | 18 | exit 1 19 | fi 20 | done 21 | 22 | -------------------------------------------------------------------------------- /examples/advanced-marker/libs/format-currency.ts: -------------------------------------------------------------------------------- 1 | export const getFormattedCurrency = priceString => { 2 | const price = parseFloat(priceString.replace('$', '')); 3 | return formatCurrency(price); 4 | }; 5 | 6 | const formatCurrency = (price: number) => { 7 | const formatter = new Intl.NumberFormat('en-US', { 8 | style: 'currency', 9 | currency: 'USD', 10 | minimumFractionDigits: 0, 11 | maximumFractionDigits: 0 12 | }); 13 | 14 | return formatter.format(price); 15 | }; 16 | -------------------------------------------------------------------------------- /examples/map-3d/src/style.css: -------------------------------------------------------------------------------- 1 | [class$='alpha-banner'] { 2 | right: auto; 3 | left: 50%; 4 | translate: -50% 0; 5 | top: auto; 6 | bottom: 1.5rem; 7 | max-width: 600px; 8 | font-size: 0.875em; 9 | border-radius: 10px; 10 | } 11 | 12 | .minimap { 13 | position: absolute; 14 | bottom: 1.5rem; 15 | right: 1.5rem; 16 | 17 | width: 50%; 18 | max-width: 320px; 19 | aspect-ratio: 1; 20 | border-radius: 10px; 21 | overflow: hidden; 22 | box-shadow: 5px 2px 20px rgb(0, 0, 0, 0.6); 23 | } 24 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/src/castles.ts: -------------------------------------------------------------------------------- 1 | import {FeatureCollection, Point} from 'geojson'; 2 | 3 | export type CastleFeatureProps = { 4 | name: string; 5 | wikipedia: string; 6 | wikidata: string; 7 | }; 8 | 9 | export type CastlesGeojson = FeatureCollection; 10 | 11 | export async function loadCastlesGeojson(): Promise { 12 | const url = new URL('../data/castles.json', import.meta.url); 13 | 14 | return await fetch(url).then(res => res.json()); 15 | } 16 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/components/header.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | import styles from './header.module.css'; 4 | 5 | export default function Header() { 6 | return ( 7 |
8 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/remix/app/components/header/header.tsx: -------------------------------------------------------------------------------- 1 | import {Link} from '@remix-run/react'; 2 | 3 | import styles from './header.module.css'; 4 | 5 | export default function Header() { 6 | return ( 7 |
8 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/_template/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/basic-map/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/drawing/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/geometry/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/heatmap/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/map-3d/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/autocomplete/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/directions/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/map-control/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/multiple-maps/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/places-ui-kit/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/routes-api/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/static-map/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/advanced-marker/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/change-map-styles/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/heatmap/src/earthquakes.ts: -------------------------------------------------------------------------------- 1 | import {FeatureCollection, Point} from 'geojson'; 2 | 3 | export type EarthquakeProps = { 4 | id: string; 5 | mag: number; 6 | time: number; 7 | felt: number | null; 8 | tsunami: 0 | 1; 9 | }; 10 | 11 | export type EarthquakesGeojson = FeatureCollection; 12 | 13 | export async function loadEarthquakeGeojson(): Promise { 14 | const url = new URL('../data/earthquakes.json', import.meta.url); 15 | 16 | return await fetch(url).then(res => res.json()); 17 | } 18 | -------------------------------------------------------------------------------- /examples/homepage-header/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/marker-clustering/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/src/components/popover/popover.css: -------------------------------------------------------------------------------- 1 | gmp-popover { 2 | color-scheme: light; 3 | --gmp-popover-pixel-offset-y: -48px; 4 | } 5 | 6 | gmp-popover gmp-place-details-compact { 7 | width: 352px; 8 | border-color: transparent; 9 | } 10 | 11 | .custom { 12 | --gmp-mat-color-on-surface: #e37400; 13 | --gmp-mat-color-on-secondary-container: white; 14 | --gmp-mat-font-family: 'Bree Serif'; 15 | 16 | --gmp-mat-color-secondary-container: #e37400; 17 | --gmp-mat-font-body-small: 500 0.75em / 1.3333333333 'Roboto'; 18 | } 19 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@googlemaps/markerclusterer": "^2.5.1", 5 | "@types/geojson": "^7946.0.14", 6 | "@types/supercluster": "^7.1.3", 7 | "@vis.gl/react-google-maps": "latest", 8 | "react": "^19.0.0", 9 | "react-dom": "^19.0.0", 10 | "supercluster": "^8.0.1", 11 | "vite": "^7.1.7" 12 | }, 13 | "scripts": { 14 | "start": "vite", 15 | "start-local": "vite --config ../vite.config.local.js", 16 | "build": "vite build" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/react-wrapper-migration/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/src/style.css: -------------------------------------------------------------------------------- 1 | .custom-marker { 2 | transition: all 200ms ease-in-out; 3 | } 4 | 5 | .custom-html-content { 6 | width: 25px; 7 | height: 25px; 8 | transition: all 200ms ease-in-out; 9 | border: 1px solid #ffa700; 10 | border-radius: 4px; 11 | background: #0057e7; 12 | } 13 | 14 | .custom-html-content.selected { 15 | background: #22ccff; 16 | } 17 | 18 | .visualization-marker { 19 | width: 8px; 20 | height: 8px; 21 | background: #ffa700; 22 | border-radius: 50%; 23 | border: 1px solid #0057e7; 24 | } 25 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/extended-component-library/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/extended-component-library/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@googlemaps/extended-component-library": "^0.6.11", 5 | "@vis.gl/react-google-maps": "latest", 6 | "react": "^19.0.0", 7 | "react-dom": "^19.0.0", 8 | "vite": "^7.1.7" 9 | }, 10 | "scripts": { 11 | "start": "vite", 12 | "start-local": "vite --config ../vite.config.local.js", 13 | "build": "vite build" 14 | }, 15 | "devDependencies": { 16 | "@types/node": "^24.6.1", 17 | "@types/react-dom": "^19.0.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/map-3d/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["./types", "../../types", "./node_modules/@types"], 4 | "strict": true, 5 | "sourceMap": true, 6 | "noEmit": true, 7 | "noImplicitAny": true, 8 | "module": "ESNext", 9 | "moduleResolution": "bundler", 10 | "esModuleInterop": true, 11 | "target": "ES2020", 12 | "lib": ["es2020", "dom"], 13 | "jsx": "react", 14 | "skipLibCheck": true 15 | }, 16 | "exclude": ["./dist", "./node_modules"], 17 | "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /examples/advanced-marker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["./types", "../../types", "./node_modules/@types"], 4 | "strict": true, 5 | "sourceMap": true, 6 | "noEmit": true, 7 | "noImplicitAny": true, 8 | "module": "ESNext", 9 | "moduleResolution": "bundler", 10 | "esModuleInterop": true, 11 | "target": "ES2020", 12 | "lib": ["es2020", "dom"], 13 | "jsx": "react", 14 | "skipLibCheck": true 15 | }, 16 | "exclude": ["./dist", "./node_modules"], 17 | "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /src/components/static-map.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export {createStaticMapsUrl} from '../libraries/create-static-maps-url'; 4 | export * from '../libraries/create-static-maps-url/types'; 5 | 6 | /** 7 | * Props for the StaticMap component 8 | */ 9 | export type StaticMapProps = { 10 | url: string; 11 | className?: string; 12 | }; 13 | 14 | export const StaticMap = (props: StaticMapProps) => { 15 | const {url, className} = props; 16 | 17 | if (!url) throw new Error('URL is required'); 18 | 19 | return ; 20 | }; 21 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["./types", "../../types", "./node_modules/@types"], 4 | "strict": true, 5 | "sourceMap": true, 6 | "noEmit": true, 7 | "noImplicitAny": true, 8 | "module": "ESNext", 9 | "moduleResolution": "bundler", 10 | "esModuleInterop": true, 11 | "target": "ES2020", 12 | "lib": ["es2020", "dom"], 13 | "jsx": "react", 14 | "skipLibCheck": true 15 | }, 16 | "exclude": ["./dist", "./node_modules"], 17 | "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /.ocularrc.js: -------------------------------------------------------------------------------- 1 | const {resolve} = require('path'); 2 | 3 | const config = { 4 | lint: { 5 | paths: ['src', 'test', 'examples'] 6 | }, 7 | 8 | typescript: { 9 | project: 'tsconfig.json' 10 | }, 11 | 12 | aliases: { 13 | 'react-map-gl/test': resolve('./test'), 14 | 'react-map-gl': resolve('./src') 15 | }, 16 | 17 | browserTest: { 18 | server: {wait: 5000} 19 | }, 20 | 21 | entry: { 22 | test: 'test/node.js', 23 | 'test-browser': 'test/browser.js', 24 | size: ['test/size/all.js', 'test/size/map.js'] 25 | } 26 | }; 27 | 28 | module.exports = config; 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["./types", "./node_modules/@types"], 4 | "strict": true, 5 | "sourceMap": true, 6 | "noEmit": true, 7 | "noImplicitAny": true, 8 | "module": "ESNext", 9 | "moduleResolution": "bundler", 10 | "esModuleInterop": true, 11 | "target": "ES2015", 12 | "lib": ["es2022", "dom"], 13 | "jsx": "react", 14 | 15 | "skipLibCheck": true, 16 | "paths": { 17 | "@vis.gl/react-google-maps": ["./src"] 18 | } 19 | }, 20 | "include": ["./src/**/*"], 21 | "exclude": ["./dist", "./node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /src/hooks/use-prop-binding.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react'; 2 | 3 | /** 4 | * Internally used to copy values from props into API-Objects 5 | * whenever they change. 6 | * 7 | * @example 8 | * usePropBinding(marker, 'position', position); 9 | * 10 | * @internal 11 | */ 12 | export function usePropBinding( 13 | object: T | null, 14 | prop: K, 15 | value: T[K] 16 | ) { 17 | useEffect(() => { 18 | if (!object) return; 19 | 20 | // eslint-disable-next-line react-hooks/immutability 21 | object[prop] = value; 22 | }, [object, prop, value]); 23 | } 24 | -------------------------------------------------------------------------------- /src/hooks/use-dom-event-listener.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import {useEffect} from 'react'; 3 | 4 | /** 5 | * Internally used to bind events to DOM nodes. 6 | * @internal 7 | */ 8 | export function useDomEventListener void>( 9 | target?: Node | null, 10 | name?: string, 11 | callback?: T | null 12 | ) { 13 | useEffect(() => { 14 | if (!target || !name || !callback) return; 15 | 16 | target.addEventListener(name, callback); 17 | 18 | return () => target.removeEventListener(name, callback); 19 | }, [target, name, callback]); 20 | } 21 | -------------------------------------------------------------------------------- /examples/static-map/src/static-map-1.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StaticMap, createStaticMapsUrl} from '@vis.gl/react-google-maps'; 3 | 4 | const API_KEY = 5 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 6 | 7 | export default function StaticMap1() { 8 | const staticMapsUrl = createStaticMapsUrl({ 9 | apiKey: API_KEY, 10 | scale: 2, 11 | width: 600, 12 | height: 600, 13 | center: {lat: 53.555570296010295, lng: 10.008892744638956}, 14 | zoom: 8, 15 | language: 'en' 16 | }); 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /examples/remix/vite.config.ts: -------------------------------------------------------------------------------- 1 | import {vitePlugin as remix} from '@remix-run/dev'; 2 | import {defineConfig} from 'vite'; 3 | import tsconfigPaths from 'vite-tsconfig-paths'; 4 | 5 | declare module '@remix-run/node' { 6 | interface Future { 7 | v3_singleFetch: true; 8 | } 9 | } 10 | 11 | export default defineConfig({ 12 | plugins: [ 13 | remix({ 14 | future: { 15 | v3_fetcherPersist: true, 16 | v3_relativeSplatPath: true, 17 | v3_throwAbortReason: true, 18 | v3_singleFetch: true, 19 | v3_lazyRouteDiscovery: true 20 | } 21 | }), 22 | tsconfigPaths() 23 | ] 24 | }); 25 | -------------------------------------------------------------------------------- /src/hooks/use-maps-event-listener.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import {useEffect} from 'react'; 3 | 4 | /** 5 | * Internally used to bind events to Maps JavaScript API objects. 6 | * @internal 7 | */ 8 | export function useMapsEventListener void>( 9 | target?: object | null, 10 | name?: string, 11 | callback?: T | null 12 | ) { 13 | useEffect(() => { 14 | if (!target || !name || !callback) return; 15 | 16 | const listener = google.maps.event.addListener(target, name, callback); 17 | 18 | return () => listener.remove(); 19 | }, [target, name, callback]); 20 | } 21 | -------------------------------------------------------------------------------- /examples/advanced-marker/libs/load-real-estate-listing.ts: -------------------------------------------------------------------------------- 1 | import {RealEstateListing} from '../src/types/types'; 2 | 3 | import frontImage from '../data/images/front.jpg'; 4 | import bedroomImage from '../data/images/bedroom.jpg'; 5 | import backImage from '../data/images/back.jpg'; 6 | 7 | export async function loadRealEstateListing(): Promise { 8 | const url = new URL('../data/real-estate-listing.json', import.meta.url); 9 | 10 | const listing = (await fetch(url).then(res => 11 | res.json() 12 | )) as RealEstateListing; 13 | 14 | listing.images = [frontImage, bedroomImage, backImage]; 15 | 16 | return listing; 17 | } 18 | -------------------------------------------------------------------------------- /examples/drawing/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | import {APIProvider} from '@vis.gl/react-google-maps'; 4 | 5 | import DrawingExample from './drawing-example'; 6 | 7 | const API_KEY = 8 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 9 | 10 | const App = () => { 11 | return ( 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | export default App; 19 | 20 | export function renderToDom(container: HTMLElement) { 21 | const root = createRoot(container); 22 | 23 | root.render(); 24 | } 25 | -------------------------------------------------------------------------------- /examples/remix/app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * By default, Remix will handle hydrating your app on the client for you. 3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ 4 | * For more information, see https://remix.run/file-conventions/entry.client 5 | */ 6 | 7 | import {RemixBrowser} from '@remix-run/react'; 8 | import {startTransition, StrictMode} from 'react'; 9 | import {hydrateRoot} from 'react-dom/client'; 10 | 11 | startTransition(() => { 12 | hydrateRoot( 13 | document, 14 | 15 | 16 | 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/__tests__/__utils__/wait-for-spy.ts: -------------------------------------------------------------------------------- 1 | import {waitFor} from '@testing-library/react'; 2 | 3 | /** 4 | * Waits for a specified spy/mock-function to be called and returns the 5 | * arguments passed to the last call. 6 | * @param spy 7 | */ 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | export async function waitForSpy any>( 10 | spy: 11 | | jest.Mock, Parameters> 12 | | jest.SpyInstance, Parameters> 13 | ): Promise | undefined> { 14 | await waitFor(() => expect(spy).toHaveBeenCalled()); 15 | 16 | return spy.mock.lastCall; 17 | } 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | version: 2 3 | updates: 4 | - package-ecosystem: 'npm' 5 | labels: ['dependencies'] 6 | directory: '/' 7 | schedule: 8 | interval: 'weekly' 9 | groups: 10 | dependencies: 11 | update-types: 12 | - 'minor' 13 | - 'patch' 14 | 15 | - package-ecosystem: 'npm' 16 | labels: ['dependencies', 'website'] 17 | directory: './website' 18 | schedule: 19 | interval: 'weekly' 20 | groups: 21 | dependencies: 22 | update-types: 23 | - 'minor' 24 | - 'patch' 25 | -------------------------------------------------------------------------------- /examples/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "cp tsconfig.sandbox.json tsconfig.json && next dev", 4 | "start-local": "cp tsconfig.local.json tsconfig.json && next dev", 5 | "build": "cp tsconfig.sandbox.json tsconfig.json && next build", 6 | "serve": "next start", 7 | "lint": "next lint" 8 | }, 9 | "dependencies": { 10 | "@vis.gl/react-google-maps": "latest", 11 | "next": "^15.5.4" 12 | }, 13 | "devDependencies": { 14 | "@types/node": "^24.6.1", 15 | "@types/react": "^19.1.16", 16 | "@types/react-dom": "^19.1.9", 17 | "eslint": "^9.36.0", 18 | "eslint-config-next": "^15.5.4", 19 | "typescript": "^5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/src/moving-marker.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import {Marker} from '@vis.gl/react-google-maps'; 3 | 4 | export const MovingMarker = () => { 5 | const [position, setPosition] = useState({ 6 | lat: 0, 7 | lng: 0 8 | }); 9 | 10 | useEffect(() => { 11 | const interval = setInterval(() => { 12 | const t = performance.now(); 13 | const lat = Math.sin(t / 2000) * 5; 14 | const lng = Math.cos(t / 3000) * 5; 15 | 16 | setPosition({lat, lng}); 17 | }, 200); 18 | 19 | return () => clearInterval(interval); 20 | }); 21 | 22 | return ; 23 | }; 24 | -------------------------------------------------------------------------------- /examples/remix/app/components/map/map.client.tsx: -------------------------------------------------------------------------------- 1 | import {APIProvider, Map} from '@vis.gl/react-google-maps'; 2 | 3 | import styles from './map.module.css'; 4 | 5 | export default function MyMap() { 6 | const API_KEY = 7 | (process.env.GOOGLE_MAPS_API_KEY as string) ?? 8 | globalThis.GOOGLE_MAPS_API_KEY; 9 | 10 | return ( 11 |
12 | 13 | 20 | 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/advanced-marker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`map and marker-library loaded anchoring with legacy API should warn when using anchorLeft/Top 1`] = ` 4 | [ 5 | [ 6 | "AdvancedMarker: The anchorLeft and anchorTop props are only supported in Google Maps API version 3.62 and above. The current version is 3.61.0.", 7 | ], 8 | ] 9 | `; 10 | 11 | exports[`map and marker-library loaded anchoring with modern API anchorLeft/anchorTop should have precedence over anchorPoint 1`] = ` 12 | [ 13 | [ 14 | "AdvancedMarker: the anchorPoint prop is ignored when anchorLeft and/or anchorTop are set.", 15 | ], 16 | ] 17 | `; 18 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import {APIProvider, Map} from '@vis.gl/react-google-maps'; 4 | 5 | import styles from './page.module.css'; 6 | 7 | export default function Home() { 8 | const API_KEY = 9 | (process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY as string) ?? 10 | globalThis.GOOGLE_MAPS_API_KEY; 11 | 12 | return ( 13 |
14 | 15 | 22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/libraries/__mocks__/google-maps-api-loader.ts: -------------------------------------------------------------------------------- 1 | import type {GoogleMapsApiLoader as ActualLoader} from '../google-maps-api-loader'; 2 | 3 | // FIXME: this should no longer be needed with the next version of @googlemaps/jest-mocks 4 | import {importLibraryMock} from './lib/import-library-mock'; 5 | import {APILoadingStatus} from '../api-loading-status'; 6 | 7 | export class GoogleMapsApiLoader { 8 | static loadingStatus: APILoadingStatus = APILoadingStatus.LOADED; 9 | static load: typeof ActualLoader.load = jest.fn( 10 | (_, onLoadingStatusChange) => { 11 | google.maps.importLibrary = importLibraryMock; 12 | onLoadingStatusChange(APILoadingStatus.LOADED); 13 | return Promise.resolve(); 14 | } 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Security updates are applied only to the latest release. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. 10 | 11 | Please disclose it at [security advisory](https://github.com/visgl/react-google-maps/security/advisories/new). 12 | 13 | This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure. 14 | -------------------------------------------------------------------------------- /examples/static-map/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Static Map

7 |

Static Map usage examples.

8 | 21 |
22 | ); 23 | } 24 | 25 | export default React.memo(ControlPanel); 26 | -------------------------------------------------------------------------------- /src/custom-elements-types/airQuality.d.ts: -------------------------------------------------------------------------------- 1 | import 'google.maps'; 2 | import type {CustomElement} from './utils'; 3 | 4 | declare module 'react' { 5 | namespace JSX { 6 | interface IntrinsicElements { 7 | 'gmp-air-quality': CustomElement< 8 | { 9 | location?: 10 | | google.maps.LatLng 11 | | google.maps.LatLngLiteral 12 | | google.maps.LatLngAltitude 13 | | google.maps.LatLngAltitudeLiteral 14 | | string 15 | | null; 16 | 17 | requestedLanguage?: string | null; 18 | 19 | // html-attribute versions 20 | 'requested-language'?: string; 21 | }, 22 | google.maps.airQuality.AirQualityElement 23 | >; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/vite.config.local.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | import {resolve} from 'node:path'; 3 | 4 | export default defineConfig(({mode}) => { 5 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 6 | 7 | return { 8 | define: { 9 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 10 | }, 11 | resolve: { 12 | alias: { 13 | '@vis.gl/react-google-maps/examples.js': resolve( 14 | '../../website/static/scripts/examples.js' 15 | ), 16 | '@vis.gl/react-google-maps/examples.css': resolve( 17 | '../../examples/examples.css' 18 | ), 19 | '@vis.gl/react-google-maps': resolve('../../src/index.ts') 20 | } 21 | } 22 | }; 23 | }); 24 | -------------------------------------------------------------------------------- /scripts/update-examples.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #rootDir=`readlink -f "$(dirname $0)/.."` 4 | rootDir="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 5 | 6 | for d in `find ${rootDir}/examples -type d -depth 1` ; do 7 | # Skip the template directory 8 | if [ "$(basename $d)" = "_template" ]; then 9 | continue 10 | fi 11 | 12 | echo ">>> updating example '$(basename $d)'" 13 | ( 14 | cd $d 15 | 16 | npm --no-progress --no-audit --no-fund --silent update 17 | to_update=`npm outdated --json | jq -r 'to_entries[] | select(.value.wanted != .value.latest) | .key'` 18 | 19 | for pkg in $to_update ; do 20 | echo " - update package ${pkg}" 21 | npm --no-progress --silent install $pkg@latest 22 | done 23 | ) 24 | 25 | done 26 | -------------------------------------------------------------------------------- /src/hooks/__tests__/__utils__/wait-for-mock-instance.ts: -------------------------------------------------------------------------------- 1 | import {waitFor} from '@testing-library/react'; 2 | import {mockInstances} from '@googlemaps/jest-mocks'; 3 | 4 | type Constructable = { 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | new (...args: any[]): unknown; 7 | }; 8 | 9 | /** 10 | * Uses `waitFor` to detect when a specified @googlemaps/jest-mocks object has 11 | * been created and returns the last item from the list of mock objects 12 | * for that type. 13 | * @param ctor 14 | */ 15 | export async function waitForMockInstance( 16 | ctor: T 17 | ): Promise> { 18 | await waitFor(() => expect(mockInstances.get(ctor)).not.toHaveLength(0)); 19 | 20 | return mockInstances.get(ctor).at(-1)!; 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/test-website.yml: -------------------------------------------------------------------------------- 1 | name: Website Test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | name: Build Website 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | 12 | - name: Setup node 13 | uses: actions/setup-node@v4 14 | with: 15 | node-version: 20 16 | cache: npm 17 | cache-dependency-path: ./website/package-lock.json 18 | 19 | - name: Install dependencies 20 | working-directory: ./website 21 | run: npm ci 22 | 23 | - name: Build website 24 | working-directory: ./website 25 | run: | 26 | npm run build 27 | env: 28 | GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} 29 | -------------------------------------------------------------------------------- /src/components/__tests__/__utils__/wait-for-mock-instance.ts: -------------------------------------------------------------------------------- 1 | import {waitFor} from '@testing-library/react'; 2 | import {mockInstances} from '@googlemaps/jest-mocks'; 3 | 4 | type Constructable = { 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | new (...args: any[]): unknown; 7 | }; 8 | 9 | /** 10 | * Uses `waitFor` to detect when a specified @googlemaps/jest-mocks object has 11 | * been created and returns the last item from the list of mock objects 12 | * for that type. 13 | * @param ctor 14 | */ 15 | export async function waitForMockInstance( 16 | ctor: T 17 | ): Promise> { 18 | await waitFor(() => expect(mockInstances.get(ctor)).not.toHaveLength(0)); 19 | 20 | return mockInstances.get(ctor).at(-1)!; 21 | } 22 | -------------------------------------------------------------------------------- /examples/extended-component-library/src/app.css: -------------------------------------------------------------------------------- 1 | .App { 2 | --gmpx-color-surface: #f6f5ff; 3 | --gmpx-color-on-primary: #f8e8ff; 4 | --gmpx-color-on-surface: #000; 5 | --gmpx-color-on-surface-variant: #636268; 6 | --gmpx-color-primary: #8a5cf4; 7 | --gmpx-fixed-panel-height-column-layout: 420px; 8 | --gmpx-fixed-panel-width-row-layout: 340px; 9 | 10 | height: 100%; 11 | width: 100%; 12 | background: var(--gmpx-color-surface); 13 | inset: 0; 14 | } 15 | 16 | .MainContainer { 17 | display: flex; 18 | flex-direction: column; 19 | } 20 | 21 | .SplitLayoutContainer { 22 | height: 100%; 23 | } 24 | 25 | .CollegePicker { 26 | --gmpx-color-surface: #fff; 27 | flex-grow: 1; 28 | margin: 1rem; 29 | } 30 | 31 | .CloseButton { 32 | display: block; 33 | margin: 1rem; 34 | } 35 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/estimate-camera-position.ts: -------------------------------------------------------------------------------- 1 | import {Map3DCameraProps} from '../map-3d'; 2 | import {destination, getCoords, point} from '@turf/turf'; 3 | 4 | export function estimateCameraPosition( 5 | camera3dProps: Map3DCameraProps 6 | ): google.maps.LatLngAltitudeLiteral { 7 | const {center, heading, tilt, range} = camera3dProps; 8 | 9 | const tiltRad = (tilt / 180) * Math.PI; 10 | const height = range * Math.cos(tiltRad); 11 | const distance = range * Math.sin(tiltRad); 12 | 13 | const [lng, lat] = getCoords( 14 | destination(point([center.lng, center.lat]), distance, heading + 180, { 15 | units: 'meters' 16 | }) 17 | ); 18 | 19 | return { 20 | lat: lat as number, 21 | lng: lng as number, 22 | altitude: center.altitude + height 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/custom-elements-types/routes.d.ts: -------------------------------------------------------------------------------- 1 | import 'google.maps'; 2 | import type {CustomElement} from './utils'; 3 | 4 | declare module 'react' { 5 | namespace JSX { 6 | interface IntrinsicElements { 7 | 'gmp-elevation': CustomElement< 8 | { 9 | path?: Array< 10 | | google.maps.LatLng 11 | | google.maps.LatLngLiteral 12 | | google.maps.LatLngAltitude 13 | | google.maps.LatLngAltitudeLiteral 14 | > | null; 15 | unitSystem?: google.maps.UnitSystem | null; 16 | 17 | // html-attribute versions 18 | 'unit-system'?: string; 19 | 20 | // emits 'gmp-requesterror' and 'gmp-load' events 21 | }, 22 | google.maps.elevation.ElevationElement 23 | >; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/nextjs/tsconfig.sandbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": [ 26 | "next-env.d.ts", 27 | "**/*.ts", 28 | "**/*.tsx", 29 | ".next/types/**/*.ts", 30 | "src/global.d.ts" 31 | ], 32 | "exclude": ["node_modules"] 33 | } 34 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/camera-position-marker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {AdvancedMarker} from '@vis.gl/react-google-maps'; 3 | 4 | import './camera-position-marker.css'; 5 | 6 | type CameraPositionMarkerProps = { 7 | position: google.maps.LatLngAltitudeLiteral; 8 | heading: number; 9 | }; 10 | 11 | export const CameraPositionMarker = ({ 12 | position, 13 | heading 14 | }: CameraPositionMarkerProps) => ( 15 | 19 | 24 | 25 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /examples/heatmap/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Heatmap 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/_template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/_template/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Example Template

7 |

8 | Add a brief description of the example here and update the link below 9 |

10 | 11 | 24 |
25 | ); 26 | } 27 | 28 | export default React.memo(ControlPanel); 29 | -------------------------------------------------------------------------------- /examples/homepage-header/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export type * from './custom-elements-types'; 3 | 4 | export * from './version'; 5 | export * from './components/advanced-marker'; 6 | export * from './components/api-provider'; 7 | export * from './components/info-window'; 8 | export * from './components/map'; 9 | export * from './components/static-map'; 10 | export * from './components/map-control'; 11 | export * from './components/marker'; 12 | export * from './components/pin'; 13 | export * from './hooks/use-api-loading-status'; 14 | export * from './hooks/use-api-is-loaded'; 15 | export * from './hooks/use-maps-library'; 16 | export * from './hooks/use-map'; 17 | export * from './libraries/lat-lng-utils'; 18 | export * from './libraries/api-loading-status'; 19 | export {limitTiltRange} from './libraries/limit-tilt-range'; 20 | -------------------------------------------------------------------------------- /examples/basic-map/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Basic Map

7 |

8 | The simplest example possible, just rendering a google map with some 9 | settings adjusted. 10 |

11 | 24 |
25 | ); 26 | } 27 | 28 | export default React.memo(ControlPanel); 29 | -------------------------------------------------------------------------------- /examples/geometry/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Map with Geometry 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/react-wrapper-migration/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/advanced-marker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Advanced Marker 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/map-3d/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Photorealistic 3D Map 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/map-control/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Custom Map Control 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type {Metadata} from 'next'; 2 | import Script from 'next/script'; 3 | 4 | import Header from './components/header'; 5 | 6 | import styles from './layout.module.css'; 7 | import './globals.css'; 8 | 9 | export const metadata: Metadata = { 10 | title: 'React Google Maps - NextJS Example' 11 | }; 12 | 13 | export default function RootLayout({ 14 | children 15 | }: Readonly<{ 16 | children: React.ReactNode; 17 | }>) { 18 | return ( 19 | 20 | 21 |
22 |
23 | {children} 24 |
25 | 26 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/change-map-styles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Changing MapIDs 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/multiple-maps/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Synchronized Maps 9 | 10 | 20 | 21 | 22 |
23 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/api-reference/hooks/use-api-loading-status.md: -------------------------------------------------------------------------------- 1 | # `useApiLoadingStatus` Hook 2 | 3 | React hook to get the current status of the API Loader. This can be used to react to loading-errors. 4 | 5 | ```tsx 6 | import {useApiLoadingStatus, APILoadingStatus} from '@vis.gl/react-google-maps'; 7 | 8 | const MyComponent = () => { 9 | const status = useApiLoadingStatus(); 10 | 11 | useEffect(() => { 12 | if (status === APILoadingStatus.FAILED) { 13 | console.log(':('); 14 | 15 | return; 16 | } 17 | }, [status]); 18 | 19 | // ... 20 | }; 21 | ``` 22 | 23 | ## Signature 24 | 25 | `useApiLoadingStatus(): APILoadingStatus` 26 | 27 | Returns the current loading-state. 28 | 29 | ## Source 30 | 31 | [`src/hooks/use-api-loading-status.ts`][src] 32 | 33 | [src]: https://github.com/visgl/react-google-maps/blob/main/src/hooks/use-api-loading-status.ts 34 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: deck.gl Interleaved Overlay 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/_template/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | import {APIProvider, Map} from '@vis.gl/react-google-maps'; 5 | import ControlPanel from './control-panel'; 6 | 7 | const API_KEY = 8 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 9 | 10 | const App = () => ( 11 | 12 | 18 | 19 | 20 | ); 21 | export default App; 22 | 23 | export function renderToDom(container: HTMLElement) { 24 | const root = createRoot(container); 25 | 26 | root.render( 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /examples/basic-map/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | import {APIProvider, Map} from '@vis.gl/react-google-maps'; 5 | import ControlPanel from './control-panel'; 6 | 7 | const API_KEY = 8 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 9 | 10 | const App = () => ( 11 | 12 | 18 | 19 | 20 | ); 21 | export default App; 22 | 23 | export function renderToDom(container: HTMLElement) { 24 | const root = createRoot(container); 25 | 26 | root.render( 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /examples/multiple-maps/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Synchronized Maps

7 |

8 | Shows how to use the controlled map component to synchronize multiple 9 | map instances. 10 |

11 | 12 | 25 |
26 | ); 27 | } 28 | 29 | export default React.memo(ControlPanel); 30 | -------------------------------------------------------------------------------- /examples/react-wrapper-migration/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | // import {Wrapper} from '@googlemaps/react-wrapper'; 5 | import {Wrapper} from './wrapper'; 6 | 7 | const API_KEY = 8 | // @ts-ignore 9 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 10 | 11 | const MapsTest = () => { 12 | return

Google Maps JavaScript API v{google.maps.version} Loaded.

; 13 | }; 14 | 15 | const App = () => ( 16 |

Status: {status}

}> 17 | 18 |
19 | ); 20 | 21 | export default App; 22 | 23 | export function renderToDom(container: HTMLElement) { 24 | const root = createRoot(container); 25 | 26 | root.render( 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /examples/drawing/src/drawing-example.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ControlPosition, Map, MapControl} from '@vis.gl/react-google-maps'; 3 | 4 | import {UndoRedoControl} from './undo-redo-control'; 5 | import {useDrawingManager} from './use-drawing-manager'; 6 | import ControlPanel from './control-panel'; 7 | 8 | const DrawingExample = () => { 9 | const drawingManager = useDrawingManager(); 10 | 11 | return ( 12 | <> 13 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default DrawingExample; 30 | -------------------------------------------------------------------------------- /examples/extended-component-library/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Extended Component Library 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/marker-clustering/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Marker Clustering 9 | 10 | 21 | 22 | 23 |
24 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/remix/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*.ts", 4 | "**/*.tsx", 5 | "**/.server/**/*.ts", 6 | "**/.server/**/*.tsx", 7 | "**/.client/**/*.ts", 8 | "**/.client/**/*.tsx" 9 | ], 10 | "compilerOptions": { 11 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 12 | "types": ["@remix-run/node", "vite/client"], 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "jsx": "react-jsx", 16 | "module": "ESNext", 17 | "moduleResolution": "Bundler", 18 | "resolveJsonModule": true, 19 | "target": "ES2022", 20 | "strict": true, 21 | "allowJs": true, 22 | "skipLibCheck": true, 23 | "forceConsistentCasingInFileNames": true, 24 | "baseUrl": ".", 25 | "paths": { 26 | "~/*": ["./app/*"] 27 | }, 28 | 29 | // Vite takes care of building everything, not tsc. 30 | "noEmit": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-google-maps-website", 3 | "scripts": { 4 | "docusaurus": "docusaurus", 5 | "start": "docusaurus start", 6 | "build": "docusaurus clear && docusaurus build", 7 | "swizzle": "docusaurus swizzle", 8 | "clear": "docusaurus clear", 9 | "serve": "docusaurus serve", 10 | "postinstall": "bash ../scripts/install-examples.sh" 11 | }, 12 | "dependencies": { 13 | "react": "^19.1.1", 14 | "react-dom": "^19.1.1", 15 | "styled-components": "^6.1.19" 16 | }, 17 | "devDependencies": { 18 | "@docusaurus/core": "^3.9.2", 19 | "@docusaurus/plugin-content-docs": "^3.9.2", 20 | "@docusaurus/preset-classic": "^3.9.2", 21 | "@easyops-cn/docusaurus-search-local": "^0.52.1", 22 | "babel-plugin-styled-components": "^2.0.0", 23 | "babel-plugin-version-inline": "^1.0.0", 24 | "glob": "^11.0.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/api-reference/hooks/use-api-is-loaded.md: -------------------------------------------------------------------------------- 1 | # `useApiIsLoaded` Hook 2 | 3 | React hook to check if the Maps JavaScript API has finished loading. 4 | 5 | ```tsx 6 | import {useApiIsLoaded} from '@vis.gl/react-google-maps'; 7 | 8 | const MyComponent = () => { 9 | const apiIsLoaded = useApiIsLoaded(); 10 | 11 | useEffect(() => { 12 | if (!apiIsLoaded) return; 13 | 14 | // when the maps library is loaded, apiIsLoaded will be true and the API can be 15 | // accessed using the global `google.maps` namespace. 16 | }, [apiIsLoaded]); 17 | 18 | // ... 19 | }; 20 | ``` 21 | 22 | ## Signature 23 | 24 | `useApiIsLoaded(): boolean` 25 | 26 | Returns a boolean indicating if the Maps JavaScript API completed loading. 27 | 28 | ## Source 29 | 30 | [`src/hooks/use-api-is-loaded.ts`][src] 31 | 32 | [src]: https://github.com/visgl/react-google-maps/blob/main/src/hooks/use-api-is-loaded.ts 33 | -------------------------------------------------------------------------------- /examples/drawing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Drawing Tools Example 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Markers and InfoWindows

7 |

8 | This example shows the different ways to add markers and infowindows to 9 | the map. 10 |

11 | 24 |
25 | ); 26 | } 27 | 28 | export default React.memo(ControlPanel); 29 | -------------------------------------------------------------------------------- /docs/api-reference/components/pin.md: -------------------------------------------------------------------------------- 1 | # `` Component 2 | 3 | The `Pin` component can be used to customize the appearance of an 4 | [`AdvancedMarker`](./advanced-marker.md) component. 5 | 6 | ## Usage 7 | 8 | ```tsx 9 | const CustomizedMarker = () => ( 10 | 11 | 12 | 13 | ); 14 | ``` 15 | 16 | ## Props 17 | 18 | The `PinProps` type mirrors the [`google.maps.PinElementOptions` interface][gmp-pin-element-options] 19 | and includes all possible options available for a Pin Element instance. 20 | 21 | [gmp-pin-element]: https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#PinElement 22 | [gmp-pin-element-options]: https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#PinElementOptions 23 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Custom Marker Clustering 9 | 10 | 21 | 22 | 23 |
24 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/static-map/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Static Map Example 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /website/src/components/example/doc-item-component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import useBaseUrl from '@docusaurus/useBaseUrl'; 4 | 5 | const DemoContainer = styled.div` 6 | position: absolute; 7 | overflow: hidden !important; 8 | left: 0; 9 | right: 0; 10 | top: 0; 11 | bottom: 0; 12 | 13 | > header { 14 | display: none; 15 | } 16 | `; 17 | 18 | /** Passed to @docusaurus/plugin-content-docs to render the mdx content */ 19 | export default function DocItem({content, route}) { 20 | const MDXComponent = content; 21 | const indexPath = useBaseUrl('/examples'); 22 | if (route.path === indexPath) { 23 | return ( 24 |
25 | 26 |
27 | ); 28 | } 29 | 30 | return ( 31 | 32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /examples/autocomplete/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Autocomplete Examples 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/places-ui-kit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Places UI Kit Example 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/routes-api/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Routes API Rendering

7 |

8 | This is an example to show how to retrieve a route from the new Routes 9 | API and visualize it with polylines and advanced markers. 10 |

11 | 12 | 25 |
26 | ); 27 | } 28 | 29 | export default React.memo(ControlPanel); 30 | -------------------------------------------------------------------------------- /examples/basic-map/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Basic Map Component Example 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/static-map/src/static-map-3.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StaticMap, createStaticMapsUrl} from '@vis.gl/react-google-maps'; 3 | 4 | const API_KEY = 5 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 6 | 7 | export default function StaticMap3() { 8 | const staticMapsUrl = createStaticMapsUrl({ 9 | apiKey: API_KEY, 10 | scale: 2, 11 | width: 600, 12 | height: 600, 13 | mapType: 'hybrid', 14 | format: 'jpg', 15 | paths: [ 16 | { 17 | color: '0xff1493', 18 | fillcolor: '0xffff00', 19 | coordinates: [ 20 | {lat: 52.5, lng: 10}, 21 | 'Berlin, Germany', 22 | 'Hamburg, Germany' 23 | ] 24 | }, 25 | { 26 | coordinates: [{lat: 52.5, lng: 10}, 'Leipzig, Germany'] 27 | } 28 | ] 29 | }); 30 | 31 | return ; 32 | } 33 | -------------------------------------------------------------------------------- /src/libraries/lat-lng-utils.ts: -------------------------------------------------------------------------------- 1 | export function isLatLngLiteral( 2 | obj: unknown 3 | ): obj is google.maps.LatLngLiteral { 4 | if (!obj || typeof obj !== 'object') return false; 5 | if (!('lat' in obj && 'lng' in obj)) return false; 6 | 7 | return Number.isFinite(obj.lat) && Number.isFinite(obj.lng); 8 | } 9 | 10 | export function latLngEquals( 11 | a: google.maps.LatLngLiteral | google.maps.LatLng | undefined | null, 12 | b: google.maps.LatLngLiteral | google.maps.LatLng | undefined | null 13 | ): boolean { 14 | if (!a || !b) return false; 15 | const A = toLatLngLiteral(a); 16 | const B = toLatLngLiteral(b); 17 | if (A.lat !== B.lat || A.lng !== B.lng) return false; 18 | return true; 19 | } 20 | 21 | export function toLatLngLiteral( 22 | obj: google.maps.LatLngLiteral | google.maps.LatLng 23 | ): google.maps.LatLngLiteral { 24 | if (isLatLngLiteral(obj)) return obj; 25 | 26 | return obj.toJSON(); 27 | } 28 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Advanced Marker interaction 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/nextjs/tsconfig.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"], 23 | // Use local files when starting with `npm run start-local` 24 | "@vis.gl/react-google-maps": ["../../src/index"] 25 | } 26 | }, 27 | "include": [ 28 | "next-env.d.ts", 29 | "**/*.ts", 30 | "**/*.tsx", 31 | ".next/types/**/*.ts", 32 | "src/global.d.ts" 33 | ], 34 | "exclude": ["node_modules"] 35 | } 36 | -------------------------------------------------------------------------------- /examples/autocomplete/src/autocomplete-result.tsx: -------------------------------------------------------------------------------- 1 | import {AdvancedMarker, Pin, useMap} from '@vis.gl/react-google-maps'; 2 | import React, {useEffect} from 'react'; 3 | 4 | interface Props { 5 | place: google.maps.places.Place | null; 6 | } 7 | 8 | const AutocompleteResult = ({place}: Props) => { 9 | const map = useMap(); 10 | 11 | // adjust the viewport of the map when the place is changed 12 | useEffect(() => { 13 | if (!map || !place) return; 14 | if (place.viewport) map.fitBounds(place.viewport); 15 | }, [map, place]); 16 | 17 | if (!place) return null; 18 | 19 | // add a marker for the selected place 20 | return ( 21 | 22 | 26 | 27 | ); 28 | }; 29 | 30 | export default React.memo(AutocompleteResult); 31 | -------------------------------------------------------------------------------- /src/libraries/limit-tilt-range.ts: -------------------------------------------------------------------------------- 1 | const mapLinear = (x: number, a1: number, a2: number, b1: number, b2: number) => 2 | b1 + ((x - a1) * (b2 - b1)) / (a2 - a1); 3 | 4 | const getMapMaxTilt = (zoom: number) => { 5 | if (zoom <= 10) { 6 | return 30; 7 | } 8 | if (zoom >= 15.5) { 9 | return 67.5; 10 | } 11 | 12 | // range [10...14] 13 | if (zoom <= 14) { 14 | return mapLinear(zoom, 10, 14, 30, 45); 15 | } 16 | 17 | // range [14...15.5] 18 | return mapLinear(zoom, 14, 15.5, 45, 67.5); 19 | }; 20 | 21 | /** 22 | * Function to limit the tilt range of the Google map when updating the view state 23 | */ 24 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 25 | export const limitTiltRange = ({viewState}: any) => { 26 | const pitch = viewState.pitch; 27 | const gmZoom = viewState.zoom + 1; 28 | const maxTilt = getMapMaxTilt(gmZoom); 29 | 30 | return {...viewState, fovy: 25, pitch: Math.min(maxTilt, pitch)}; 31 | }; 32 | -------------------------------------------------------------------------------- /website/src/components/home/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 3 | import { 4 | Banner, 5 | BannerContainer, 6 | HeroExampleContainer, 7 | ProjectName, 8 | GetStartedLink 9 | } from './styled'; 10 | 11 | export default function renderPage({HeroExample, children}) { 12 | const {siteConfig} = useDocusaurusContext(); 13 | 14 | // Note: The Layout "wrapper" component adds header and footer etc 15 | return ( 16 | <> 17 | 18 | 19 | {HeroExample && } 20 | 21 | 22 | {siteConfig.title} 23 |

{siteConfig.tagline}

24 | GET STARTED 25 |
26 |
27 | {children} 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /website/src/examples-sidebar.js: -------------------------------------------------------------------------------- 1 | const sidebars = { 2 | examplesSidebar: [ 3 | { 4 | type: 'doc', 5 | label: 'Overview', 6 | id: 'index' 7 | }, 8 | { 9 | type: 'category', 10 | label: 'Examples', 11 | collapsed: false, 12 | items: [ 13 | 'basic-map', 14 | 'change-map-styles', 15 | 'markers-and-infowindows', 16 | 'advanced-marker', 17 | 'advanced-marker-interaction', 18 | 'map-control', 19 | 'multiple-maps', 20 | 'marker-clustering', 21 | 'custom-marker-clustering', 22 | 'geometry', 23 | 'heatmap', 24 | 'drawing', 25 | 'autocomplete', 26 | 'directions', 27 | 'routes-api', 28 | 'deckgl-overlay', 29 | 'map-3d', 30 | 'extended-component-library', 31 | 'static-map', 32 | 'places-ui-kit' 33 | ] 34 | } 35 | ] 36 | }; 37 | 38 | module.exports = sidebars; 39 | -------------------------------------------------------------------------------- /examples/drawing/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Drawing Tools Example

7 |

8 | Shows how to use the drawing tools of the Maps JavaScript API and 9 | implements an undo/redo flow to show how to integrate the drawing 10 | manager and its events into the state of a react-application. 11 |

12 | 25 |
26 | ); 27 | } 28 | 29 | export default React.memo(ControlPanel); 30 | -------------------------------------------------------------------------------- /src/libraries/create-static-maps-url/helpers.ts: -------------------------------------------------------------------------------- 1 | import {StaticMapsLocation} from './types'; 2 | 3 | /** 4 | * Formats a location into a string representation suitable for Google Static Maps API. 5 | * 6 | * @param location - The location to format, can be either a string or an object with lat/lng properties 7 | * @returns A string representation of the location in the format "lat,lng" or the original string 8 | * 9 | * @example 10 | * // Returns "40.714728,-73.998672" 11 | * formatLocation({ lat: 40.714728, lng: -73.998672 }) 12 | * 13 | * @example 14 | * // Returns "New York, NY" 15 | * formatLocation("New York, NY") 16 | */ 17 | export function formatLocation(location: StaticMapsLocation): string { 18 | return typeof location === 'string' 19 | ? location 20 | : `${location.lat},${location.lng}`; 21 | } 22 | 23 | // Used for removing the leading pipe from the param string 24 | export function formatParam(string: string) { 25 | return string.slice(1); 26 | } 27 | -------------------------------------------------------------------------------- /src/components/map/auth-failure-message.tsx: -------------------------------------------------------------------------------- 1 | import React, {CSSProperties, FunctionComponent} from 'react'; 2 | 3 | export const AuthFailureMessage: FunctionComponent = () => { 4 | const style: CSSProperties = { 5 | position: 'absolute', 6 | top: 0, 7 | left: 0, 8 | bottom: 0, 9 | right: 0, 10 | zIndex: 999, 11 | display: 'flex', 12 | flexFlow: 'column nowrap', 13 | textAlign: 'center', 14 | justifyContent: 'center', 15 | fontSize: '.8rem', 16 | color: 'rgba(0,0,0,0.6)', 17 | background: '#dddddd', 18 | padding: '1rem 1.5rem' 19 | }; 20 | 21 | return ( 22 |
23 |

Error: AuthFailure

24 |

25 | A problem with your API key prevents the map from rendering correctly. 26 | Please make sure the value of the APIProvider.apiKey prop 27 | is correct. Check the error-message in the console for further details. 28 |

29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Request for a new feature or enhancement 3 | title: "[Feat]" 4 | labels: feature 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Tell us what you are using `@vis.gl/react-google-maps` for and how we can make it better. 10 | 11 | This project is maintained by volunteers and sponsoring companies. While we cannot promise a timeline for any specific feature, we try to prioritize those that will benefit the most users. 12 | - type: textarea 13 | attributes: 14 | label: Target Use Case 15 | description: How would this benefit you and other developers? 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Proposal 21 | description: How would this feature work? If it's a new API, use code samples to show how it will be used. If it's visual, link to an image that illustrate the desired effect. 22 | validations: 23 | required: true 24 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/src/components/overlay/overlay.css: -------------------------------------------------------------------------------- 1 | .overlay { 2 | position: absolute; 3 | left: 0; 4 | top: 0; 5 | width: 352px; 6 | height: 600px; 7 | max-height: calc(100vh - 48px); 8 | display: flex; 9 | flex-direction: column; 10 | gap: 8px; 11 | margin: 24px 30px; 12 | } 13 | 14 | .overlay-header { 15 | flex-shrink: 0; 16 | background: white; 17 | border-radius: 16px; 18 | overflow: hidden; 19 | } 20 | 21 | .overlay-header gmp-place-details-compact.custom { 22 | --gmp-mat-color-surface: #ffeffe; 23 | --gmp-mat-font-family: 'Bree Serif'; 24 | --gmp-mat-color-on-surface: #ff0fa7; 25 | --gmp-mat-color-secondary-container: #ff0fa7; 26 | --gmp-mat-color-on-secondary-container: white; 27 | --gmp-mat-font-body-small: 500 0.75em / 1.3333333333 'Google Sans'; 28 | } 29 | 30 | gmp-place-details-compact { 31 | border-radius: 0; 32 | } 33 | 34 | gmp-place-details-compact, 35 | gmp-place-search { 36 | color-scheme: light; 37 | border-color: transparent; 38 | } 39 | -------------------------------------------------------------------------------- /examples/advanced-marker/icons/real-estate-icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const RealEstateIcon = () => ( 4 | 10 | 11 | 17 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /examples/advanced-marker/src/types/types.ts: -------------------------------------------------------------------------------- 1 | export interface RealEstateListing { 2 | uuid: string; 3 | details: ListingDetails; 4 | images: string[]; 5 | } 6 | 7 | export interface ListingDetails { 8 | property_type: string; 9 | property_address: string; 10 | property_bedrooms: number; 11 | property_bathrooms: number; 12 | property_square_feet: string; 13 | property_lot_size: string; 14 | property_price: string; 15 | property_year_built: number; 16 | property_adjective: string; 17 | property_material: string; 18 | property_garage: false; 19 | property_features: string[]; 20 | property_accessibility: string; 21 | property_eco_features: string; 22 | property_has_view: false; 23 | local_amenities: string; 24 | transport_access: string; 25 | ambiance: string; 26 | latitude: number; 27 | longitude: number; 28 | img_weather: string; 29 | listing_title: string; 30 | listing_description: string; 31 | img_prompt_front: string; 32 | img_prompt_back: string; 33 | img_prompt_bedroom: string; 34 | } 35 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples for @vis.gl/react-google-maps 2 | 3 | All of our examples are fully self-contained and can be run as independent 4 | applications, even when the entire directory is copied elsewhere. Just run 5 | `npm install` and `npm start` in any example directory to start it. 6 | 7 | During development of the library, the examples can also be started using 8 | `npm run start-local`, which will run the example with the checked-out 9 | source of the library instead of the package installed via npm. 10 | 11 | All examples also contain a link to a CodeSandbox environment where the 12 | example can be played with right away. 13 | 14 | When browsing the examples on GitHub, you can also just replace `github.com` 15 | with `githubbox.com` in your url while you're in an example directory to 16 | open it in a codesandbox environment. 17 | 18 | More information about how to write examples [can be found in our 19 | documentation][writing-examples]. 20 | 21 | [writing-examples]: https://visgl.github.io/react-google-maps/docs/guides/writing-examples 22 | -------------------------------------------------------------------------------- /examples/marker-clustering/src/tree-marker.tsx: -------------------------------------------------------------------------------- 1 | import {Tree} from './trees'; 2 | import type {Marker} from '@googlemaps/markerclusterer'; 3 | import React, {useCallback} from 'react'; 4 | import {AdvancedMarker} from '@vis.gl/react-google-maps'; 5 | 6 | export type TreeMarkerProps = { 7 | tree: Tree; 8 | onClick: (tree: Tree) => void; 9 | setMarkerRef: (marker: Marker | null, key: string) => void; 10 | }; 11 | 12 | /** 13 | * Wrapper Component for an AdvancedMarker for a single tree. 14 | */ 15 | export const TreeMarker = (props: TreeMarkerProps) => { 16 | const {tree, onClick, setMarkerRef} = props; 17 | 18 | const handleClick = useCallback(() => onClick(tree), [onClick, tree]); 19 | const ref = useCallback( 20 | (marker: google.maps.marker.AdvancedMarkerElement) => 21 | setMarkerRef(marker, tree.key), 22 | [setMarkerRef, tree.key] 23 | ); 24 | 25 | return ( 26 | 27 | 🌳 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /examples/remix/vite.config.local.ts: -------------------------------------------------------------------------------- 1 | import {vitePlugin as remix} from '@remix-run/dev'; 2 | import {defineConfig, loadEnv} from 'vite'; 3 | import tsconfigPaths from 'vite-tsconfig-paths'; 4 | import {resolve} from 'node:path'; 5 | 6 | declare module '@remix-run/node' { 7 | interface Future { 8 | v3_singleFetch: true; 9 | } 10 | } 11 | 12 | export default defineConfig(({mode}) => { 13 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 14 | 15 | return { 16 | plugins: [ 17 | remix({ 18 | future: { 19 | v3_fetcherPersist: true, 20 | v3_relativeSplatPath: true, 21 | v3_throwAbortReason: true, 22 | v3_singleFetch: true, 23 | v3_lazyRouteDiscovery: true 24 | } 25 | }), 26 | tsconfigPaths() 27 | ], 28 | define: { 29 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 30 | }, 31 | resolve: { 32 | alias: { 33 | '@vis.gl/react-google-maps': resolve('../../src/index.ts') 34 | } 35 | } 36 | }; 37 | }); 38 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/src/marker-with-infowindow.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import { 3 | AdvancedMarker, 4 | InfoWindow, 5 | useAdvancedMarkerRef 6 | } from '@vis.gl/react-google-maps'; 7 | 8 | export const MarkerWithInfowindow = () => { 9 | const [infowindowOpen, setInfowindowOpen] = useState(true); 10 | const [markerRef, marker] = useAdvancedMarkerRef(); 11 | 12 | return ( 13 | <> 14 | setInfowindowOpen(true)} 17 | position={{lat: 28, lng: -82}} 18 | title={'AdvancedMarker that opens an Infowindow when clicked.'} 19 | /> 20 | {infowindowOpen && ( 21 | setInfowindowOpen(false)}> 25 | This is an example for the{' '} 26 | <AdvancedMarker />{' '} 27 | combined with an Infowindow. 28 | 29 | )} 30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /examples/static-map/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | import StaticMap1 from './static-map-1'; 5 | import StaticMap2 from './static-map-2'; 6 | import StaticMap3 from './static-map-3'; 7 | import StaticMap4 from './static-map-4'; 8 | 9 | import ControlPanel from './control-panel'; 10 | 11 | function App() { 12 | return ( 13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 | 27 | 28 |
29 | ); 30 | } 31 | 32 | export default App; 33 | 34 | export function renderToDom(container: HTMLElement) { 35 | const root = createRoot(container); 36 | 37 | root.render( 38 | 39 | 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/libraries/create-static-maps-url/types.ts: -------------------------------------------------------------------------------- 1 | export type StaticMapsLocation = google.maps.LatLngLiteral | string; 2 | 3 | export type StaticMapsMarker = { 4 | location: StaticMapsLocation; 5 | color?: string; 6 | size?: 'tiny' | 'mid' | 'small'; 7 | label?: string; 8 | icon?: string; 9 | anchor?: string; 10 | scale?: 1 | 2 | 4; 11 | }; 12 | 13 | export type StaticMapsPath = { 14 | coordinates: Array | string; 15 | weight?: number; 16 | color?: string; 17 | fillcolor?: string; 18 | geodesic?: boolean; 19 | }; 20 | 21 | export type StaticMapsApiOptions = { 22 | apiKey: string; 23 | width: number; 24 | height: number; 25 | center?: StaticMapsLocation; 26 | zoom?: number; 27 | scale?: number; 28 | format?: 'png' | 'png8' | 'png32' | 'gif' | 'jpg' | 'jpg-baseline'; 29 | mapType?: google.maps.MapTypeId; 30 | language?: string; 31 | region?: string; 32 | mapId?: string; 33 | markers?: Array; 34 | paths?: Array; 35 | visible?: Array; 36 | style?: google.maps.MapTypeStyle[]; 37 | }; 38 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/data/README.md: -------------------------------------------------------------------------------- 1 | # `castles.json` Data Source 2 | 3 | (tools: `wget`, [`jq`](https://jqlang.github.io/jq/)) 4 | 5 | ## 1. fetch data from overpass-api.de 6 | 7 | ```shell 8 | query="[out:json];nwr[historic=castle][tourism=attraction][name][wikidata];convert item ::=::,::geom=geom(),_osm_type=type(); out center;" 9 | wget -O castles-osm.json "http://overpass-api.de/api/interpreter?data=${query}" 10 | ``` 11 | 12 | ## 2. transform to proper geojson, ditching most tags 13 | 14 | ```shell 15 | jq '{ 16 | type: "FeatureCollection", 17 | features: .elements | map({ 18 | type:"Feature", 19 | id: .tags.wikidata, 20 | geometry: .geometry, 21 | properties: { 22 | name: (.tags["name:en"] // .tags.name), 23 | wikipedia:.tags.wikipedia, 24 | wikidata:.tags.wikidata 25 | } 26 | }) 27 | }' \ 28 | < castles-osm.json \ 29 | > castles.json 30 | ``` 31 | 32 | ## 3. some manual adjustments 33 | 34 | - a couple of duplicate ids removed or changed 35 | - one instance where the wikipedia-tag was broken and didn't contain the 36 | language part 37 | -------------------------------------------------------------------------------- /examples/advanced-marker/src/components/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Advanced Marker

7 |

8 | This example uses the <AdvancedMarker> component with 9 | custom hover and click states. 10 |

11 | 12 |

13 | By integrating content in the marker that would traditionally be shown 14 | in an info window, we can create a smooth and engaging user experience. 15 |

16 | 17 | 30 |
31 | ); 32 | } 33 | 34 | export default React.memo(ControlPanel); 35 | -------------------------------------------------------------------------------- /examples/map-control/src/custom-zoom-control.tsx: -------------------------------------------------------------------------------- 1 | import {ControlPosition, MapControl} from '@vis.gl/react-google-maps'; 2 | import React from 'react'; 3 | 4 | type CustomZoomControlProps = { 5 | controlPosition: ControlPosition; 6 | zoom: number; 7 | onZoomChange: (zoom: number) => void; 8 | }; 9 | 10 | export const CustomZoomControl = ({ 11 | controlPosition, 12 | zoom, 13 | onZoomChange 14 | }: CustomZoomControlProps) => { 15 | return ( 16 | 17 |
25 | 26 | onZoomChange(ev.target.valueAsNumber)} 34 | /> 35 |
36 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

deck.gl Interleaved Overlay Example

7 |

8 | An example demonstrating how an interleaved deck.gl overlay can be added 9 | to a {''} component. (using the{' '} 10 | GoogleMapsOverlay from{' '} 11 | 12 | @deck.gl/google-maps 13 | 14 | ). 15 |

16 | 29 |
30 | ); 31 | } 32 | 33 | export default React.memo(ControlPanel); 34 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/src/components/popover/popover.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {memo} from 'react'; 3 | import {PlaceDetails} from '../place-details/place-details'; 4 | 5 | import './popover.css'; 6 | import {PlaceLocationWithId} from '../../app'; 7 | 8 | type PopoverProps = { 9 | place: PlaceLocationWithId | null; 10 | useCustomStyling: boolean; 11 | }; 12 | 13 | const PopoverComponent = (props: PopoverProps) => { 14 | if (!props.place) return null; 15 | 16 | const locationString = props.place 17 | ? `${props.place.location.lat}, ${props.place.location.lng}` 18 | : ''; 19 | return ( 20 | 25 | 30 | 31 | ); 32 | }; 33 | PopoverComponent.displayName = 'Popover'; 34 | 35 | export const Popover = memo(PopoverComponent); 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Vis.gl contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/src/data.ts: -------------------------------------------------------------------------------- 1 | export type MarkerType = 'default' | 'pin' | 'html'; 2 | 3 | type MarkerData = Array<{ 4 | id: string; 5 | position: google.maps.LatLngLiteral; 6 | type: MarkerType; 7 | zIndex: number; 8 | infowindowContent?: string; 9 | }>; 10 | 11 | export const textSnippets = { 12 | default: 'This is a default AdvancedMarkerElement without custom content', 13 | pin: 'This is a AdvancedMarkerElement with custom pin-style marker', 14 | html: 'This is a AdvancedMarkerElement with custom HTML content' 15 | } as const; 16 | 17 | export function getData() { 18 | const data: MarkerData = []; 19 | 20 | // create 50 random markers 21 | for (let index = 0; index < 50; index++) { 22 | const type = 23 | Math.random() < 0.1 ? 'default' : Math.random() < 0.5 ? 'pin' : 'html'; 24 | 25 | data.push({ 26 | id: String(index), 27 | position: {lat: rnd(53.52, 53.63), lng: rnd(9.88, 10.12)}, 28 | zIndex: index, 29 | type 30 | }); 31 | } 32 | 33 | return data; 34 | } 35 | 36 | function rnd(min: number, max: number) { 37 | return Math.random() * (max - min) + min; 38 | } 39 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 13 | Example: 14 | 15 | 25 | 26 | 27 |
28 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/src/components/feature-marker.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import { 3 | AdvancedMarker, 4 | AdvancedMarkerAnchorPoint, 5 | useAdvancedMarkerRef 6 | } from '@vis.gl/react-google-maps'; 7 | import {CastleSvg} from './castle-svg'; 8 | 9 | type TreeMarkerProps = { 10 | position: google.maps.LatLngLiteral; 11 | featureId: string; 12 | onMarkerClick?: ( 13 | marker: google.maps.marker.AdvancedMarkerElement, 14 | featureId: string 15 | ) => void; 16 | }; 17 | 18 | export const FeatureMarker = ({ 19 | position, 20 | featureId, 21 | onMarkerClick 22 | }: TreeMarkerProps) => { 23 | const [markerRef, marker] = useAdvancedMarkerRef(); 24 | const handleClick = useCallback( 25 | () => onMarkerClick && onMarkerClick(marker!, featureId), 26 | [onMarkerClick, marker, featureId] 27 | ); 28 | 29 | return ( 30 | 36 | 37 | 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /examples/extended-component-library/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Google Maps Platform’s Extended Component Library

7 |

8 | Google Maps Platform’s Extended Component Library is a set of Web 9 | Components that helps developers build better maps faster, and with less 10 | effort. 11 |

12 |

13 | Ultimately, these components make it easier to read, learn, customize, 14 | and maintain maps-related code. 15 |

16 | 17 | 30 |
31 | ); 32 | } 33 | 34 | export default React.memo(ControlPanel); 35 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/src/deckgl-overlay.ts: -------------------------------------------------------------------------------- 1 | import {useMap} from '@vis.gl/react-google-maps'; 2 | import {useEffect, useMemo} from 'react'; 3 | 4 | import {GoogleMapsOverlay} from '@deck.gl/google-maps'; 5 | 6 | import type {LayersList} from '@deck.gl/core'; 7 | 8 | export type DeckglOverlayProps = {layers?: LayersList}; 9 | 10 | /** 11 | * A very simple implementation of a component that renders a list of deck.gl layers 12 | * via the GoogleMapsOverlay into the component containing it. 13 | */ 14 | export const DeckGlOverlay = ({layers}: DeckglOverlayProps) => { 15 | // the GoogleMapsOverlay can persist throughout the lifetime of the DeckGlOverlay 16 | const deck = useMemo(() => new GoogleMapsOverlay({interleaved: true}), []); 17 | 18 | // add the overlay to the map once the map is available 19 | const map = useMap(); 20 | useEffect(() => { 21 | deck.setMap(map); 22 | 23 | return () => deck.setMap(null); 24 | }, [deck, map]); 25 | 26 | // whenever the rendered data changes, the layers will be updated 27 | useEffect(() => { 28 | deck.setProps({layers}); 29 | }, [deck, layers]); 30 | 31 | // no dom rendered by this component 32 | return null; 33 | }; 34 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/src/components/dropdown/dropdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {memo} from 'react'; 3 | import {PlaceType} from '../../app'; 4 | 5 | import './dropdown.css'; 6 | 7 | type DropdownProps = { 8 | placeType: string; 9 | onPlaceTypeSelect: (placeType: PlaceType) => void; 10 | }; 11 | 12 | const DropdownComponent = (props: DropdownProps) => { 13 | const handlePlaceTypeChange = ( 14 | event: React.ChangeEvent 15 | ) => { 16 | props.onPlaceTypeSelect(event.target.value as PlaceType); 17 | }; 18 | 19 | return ( 20 |
21 | Nearby Search 22 | 32 |
33 | ); 34 | }; 35 | DropdownComponent.displayName = 'Dropdown'; 36 | 37 | export const Dropdown = memo(DropdownComponent); 38 | -------------------------------------------------------------------------------- /examples/static-map/src/static-map-2.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StaticMap, createStaticMapsUrl} from '@vis.gl/react-google-maps'; 3 | 4 | const API_KEY = 5 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 6 | 7 | export default function StaticMap2() { 8 | const staticMapsUrl = createStaticMapsUrl({ 9 | apiKey: API_KEY, 10 | scale: 2, 11 | width: 600, 12 | height: 600, 13 | mapId: '8e0a97af9386fef', 14 | format: 'png', 15 | markers: [ 16 | { 17 | location: 'Hamburg, Germany', 18 | color: '0xff1493', 19 | label: 'H', 20 | size: 'small' 21 | }, 22 | { 23 | location: {lat: 52.5, lng: 10}, 24 | color: 'blue', 25 | label: 'H' 26 | }, 27 | { 28 | location: 'Berlin, Germany', 29 | color: 'orange', 30 | icon: 'http://tinyurl.com/jrhlvu6', 31 | anchor: 'center', 32 | label: 'B', 33 | scale: 2 34 | }, 35 | { 36 | location: 'Essen, Germany', 37 | color: 'purple' 38 | } 39 | ], 40 | visible: ['Germany'] 41 | }); 42 | 43 | return ; 44 | } 45 | -------------------------------------------------------------------------------- /src/components/map/use-deckgl-camera-update.ts: -------------------------------------------------------------------------------- 1 | import {useLayoutEffect} from 'react'; 2 | 3 | export type DeckGlCompatProps = { 4 | /** 5 | * Viewport from deck.gl 6 | */ 7 | viewport?: unknown; 8 | /** 9 | * View state from deck.gl 10 | */ 11 | viewState?: Record; 12 | /** 13 | * Initial View State from deck.gl 14 | */ 15 | initialViewState?: Record; 16 | }; 17 | 18 | /** 19 | * Internal hook that updates the camera when deck.gl viewState changes. 20 | * @internal 21 | */ 22 | export function useDeckGLCameraUpdate( 23 | map: google.maps.Map | null, 24 | props: DeckGlCompatProps 25 | ) { 26 | const {viewport, viewState} = props; 27 | const isDeckGlControlled = !!viewport; 28 | 29 | useLayoutEffect(() => { 30 | if (!map || !viewState) return; 31 | 32 | const { 33 | latitude, 34 | longitude, 35 | bearing: heading, 36 | pitch: tilt, 37 | zoom 38 | } = viewState as Record; 39 | 40 | map.moveCamera({ 41 | center: {lat: latitude, lng: longitude}, 42 | heading, 43 | tilt, 44 | zoom: zoom + 1 45 | }); 46 | }, [map, viewState]); 47 | 48 | return isDeckGlControlled; 49 | } 50 | -------------------------------------------------------------------------------- /examples/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for committing if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | 42 | # Ignoring tsconfig.json here since NextJS does not provide a way yet to specify a custom tsconfig.json path 43 | # and rather hard codes the tsconfig.json path. We do want to use different tsconfig files 44 | # for this example to be able to use the local files in dev mode and the installed package 45 | # when called in Codesandbox. 46 | tsconfig.json 47 | 48 | # Ignoring the lock folder here since this is not intended 49 | # for a production build This should not be done on a real project. 50 | # https://nextjs.org/docs/app/api-reference/next-config-js/urlImports#lockfile 51 | next.lock 52 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/src/components/reference-marker/reference-marker.tsx: -------------------------------------------------------------------------------- 1 | import React, {memo, useEffect, useRef} from 'react'; 2 | 3 | type ReferenceMarkerProps = { 4 | markerOptions: google.maps.maps3d.Marker3DElementOptions; 5 | onClick: () => void; 6 | }; 7 | 8 | const ReferenceMarkerComponent = (props: ReferenceMarkerProps) => { 9 | const ref = useRef(null); 10 | 11 | useEffect(() => { 12 | const parent = ref.current?.parentElement; 13 | if (parent) { 14 | const marker = new google.maps.maps3d.Marker3DInteractiveElement( 15 | props.markerOptions 16 | ); 17 | 18 | const img = document.createElement('img'); 19 | img.src = new URL( 20 | '../reference-marker/hotel_icon.svg', 21 | import.meta.url 22 | ).toString(); 23 | 24 | const template = document.createElement('template'); 25 | template.content.append(img); 26 | 27 | marker.append(template); 28 | marker.addEventListener('gmp-click', props.onClick); 29 | 30 | parent.appendChild(marker); 31 | } 32 | }); 33 | 34 | return
; 35 | }; 36 | ReferenceMarkerComponent.displayName = 'ReferenceMarker'; 37 | 38 | export const ReferenceMarker = memo(ReferenceMarkerComponent); 39 | -------------------------------------------------------------------------------- /examples/map-control/README.md: -------------------------------------------------------------------------------- 1 | # Custom Map Control Example 2 | 3 | This is an example to show how to add custom map-controls. 4 | 5 | ## Google Maps Platform API Key 6 | 7 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 8 | See [the official documentation][get-api-key] on how to create and configure your own key. 9 | 10 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 11 | file named `.env` in the example directory with the following content: 12 | 13 | ```shell title=".env" 14 | GOOGLE_MAPS_API_KEY="" 15 | ``` 16 | 17 | 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) 18 | 19 | ## Development 20 | 21 | Go into the example-directory and run 22 | 23 | ```shell 24 | npm install 25 | ``` 26 | 27 | To start the example with the local library run 28 | 29 | ```shell 30 | npm run start-local 31 | ``` 32 | 33 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 34 | 35 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 36 | -------------------------------------------------------------------------------- /examples/static-map/README.md: -------------------------------------------------------------------------------- 1 | # Static Map Example 2 | 3 | This is an example to show how to use the `Static Map` component. 4 | 5 | ## Google Maps Platform API Key 6 | 7 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 8 | See [the official documentation][get-api-key] on how to create and configure your own key. 9 | 10 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 11 | file named `.env` in the example directory with the following content: 12 | 13 | ```shell title=".env" 14 | GOOGLE_MAPS_API_KEY="" 15 | ``` 16 | 17 | 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) 18 | 19 | ## Development 20 | 21 | Go into the example-directory and run 22 | 23 | ```shell 24 | npm install 25 | ``` 26 | 27 | To start the example with the local library run 28 | 29 | ```shell 30 | npm run start-local 31 | ``` 32 | 33 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 34 | 35 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 36 | -------------------------------------------------------------------------------- /examples/heatmap/README.md: -------------------------------------------------------------------------------- 1 | # Heatmap 2 | 3 | This uses the `useMapsLibrary` hook showing earthquake magnitude data in a 4 | heatmap. 5 | 6 | ## Google Maps Platform API key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | 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) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /src/libraries/global-style-manager.ts: -------------------------------------------------------------------------------- 1 | // Global style manager to track rendered styles and avoid duplicates 2 | class GlobalStyleManager { 3 | private renderedStyles = new Set(); 4 | private styleElement: HTMLStyleElement | null = null; 5 | 6 | private getStyleElement(): HTMLStyleElement { 7 | if (!this.styleElement) { 8 | this.styleElement = document.createElement('style'); 9 | this.styleElement.setAttribute('data-rgm-anchor-styles', ''); 10 | document.head.appendChild(this.styleElement); 11 | } 12 | return this.styleElement; 13 | } 14 | 15 | addAdvancedMarkerPointerEventsOverwrite(): void { 16 | if (this.renderedStyles.has('marker-pointer-events')) { 17 | return; 18 | } 19 | 20 | const styleElement = this.getStyleElement(); 21 | styleElement.textContent += ` 22 | gmp-advanced-marker[data-origin='rgm'] { 23 | pointer-events: none !important; 24 | } 25 | `; 26 | this.renderedStyles.add('marker-pointer-events'); 27 | } 28 | 29 | cleanup(): void { 30 | if (this.styleElement) { 31 | this.styleElement.remove(); 32 | this.styleElement = null; 33 | this.renderedStyles.clear(); 34 | } 35 | } 36 | } 37 | 38 | export const globalStyleManager = new GlobalStyleManager(); 39 | -------------------------------------------------------------------------------- /examples/multiple-maps/README.md: -------------------------------------------------------------------------------- 1 | # Synchronized Maps 2 | 3 | Shows how to use controlled mode and camera-events to synchronize 4 | multiple map instances. 5 | 6 | ## Google Maps Platform API Key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | 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) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/_template/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | This is an example to show how to setup a simple Google Maps Map with the `` component of the Google Maps React 4 | library. 5 | 6 | ## Google Maps Platform API key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | 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) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/README.md: -------------------------------------------------------------------------------- 1 | # Markers and Infowindow Example 2 | 3 | Shows the different ways to use the ``, `` and 4 | `` components. 5 | 6 | ## Google Maps Platform API Key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | 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) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | This is an example to show how to setup a simple Google Maps Map with the `` component of the Google Maps React 4 | library. 5 | 6 | ## Google Maps Platform API key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | 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) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /website/ocular-docusaurus-plugin/index.js: -------------------------------------------------------------------------------- 1 | // const util = require('util'); 2 | 3 | /* Print full webpack error; 4 | edit node_modules/react-dev-utils/formatWebpackMessages.js 5 | 6 | ``` 7 | } else if ('message' in message) { 8 | return message.message; 9 | ``` 10 | */ 11 | 12 | const util = require('util'); 13 | module.exports = function ( 14 | context, 15 | opts = { 16 | resolve: {modules: [], alias: {}}, 17 | debug: false, 18 | module: {}, 19 | plugins: [] 20 | } 21 | ) { 22 | return { 23 | name: 'ocular-docusaurus-plugin', 24 | configureWebpack(_config, isServer, utils) { 25 | const {resolve, debug, module, plugins} = opts; 26 | 27 | // Custom merging 28 | if (resolve) { 29 | if (resolve.modules) { 30 | _config.resolve.modules = resolve.modules; 31 | } 32 | Object.assign(_config.resolve.alias, resolve.alias); 33 | } 34 | 35 | // Symlink docs crash otherwise, see https://github.com/facebook/docusaurus/issues/6257 36 | _config.resolve.symlinks = false; 37 | 38 | if (isServer) { 39 | return { 40 | devtool: debug ? 'eval' : false, 41 | module, 42 | plugins, 43 | node: {__dirname: true} 44 | }; 45 | } 46 | return {module, plugins}; 47 | } 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /docs/api-reference/hooks/use-maps-library.md: -------------------------------------------------------------------------------- 1 | # `useMapsLibrary` Hook 2 | 3 | React hook to get access to the different [Maps JavaScript API libraries][gmp-libraries]. 4 | This is essentially a react-version of the `google.maps.importLibrary` function. 5 | 6 | ```tsx 7 | const MyComponent = () => { 8 | const map = useMap(); 9 | const placesLib = useMapsLibrary('places'); 10 | 11 | useEffect(() => { 12 | if (!placesLib || !map) return; 13 | 14 | const svc = new placesLib.PlacesService(map); 15 | // ... 16 | }, [placesLib, map]); 17 | 18 | // ... 19 | }; 20 | 21 | // Make sure you have wrapped the component tree with the APIProvider 22 | const App = () => ( 23 | 24 | {/* ... */} 25 | 26 | 27 | ); 28 | ``` 29 | 30 | ## Signature 31 | 32 | `useMapsLibrary(name: string): google.maps.XxxLibrary` 33 | 34 | Returns the library object as it is returned by `google.maps.importLibrary`. 35 | 36 | ### Parameters 37 | 38 | #### `name`: string (required) 39 | 40 | The name of the library that should be loaded 41 | 42 | ## Source 43 | 44 | [`src/hooks/use-maps-library.ts`][src] 45 | 46 | [gmp-libraries]: https://developers.google.com/maps/documentation/javascript/libraries 47 | [src]: https://github.com/visgl/react-google-maps/blob/main/src/hooks/use-maps-library.ts 48 | -------------------------------------------------------------------------------- /examples/change-map-styles/README.md: -------------------------------------------------------------------------------- 1 | # Map Styling Example 2 | 3 | This is an example to demonstrate changing the style of the `` component using local and cloud-based map styles as well as map types. 4 | 5 | ## Google Maps Platform API Key 6 | 7 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 8 | See [the official documentation][get-api-key] on how to create and configure your own key. 9 | 10 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 11 | file named `.env` in the example directory with the following content: 12 | 13 | ```shell title=".env" 14 | GOOGLE_MAPS_API_KEY="" 15 | ``` 16 | 17 | 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) 18 | 19 | ## Development 20 | 21 | Go into the example-directory and run 22 | 23 | ```shell 24 | npm install 25 | ``` 26 | 27 | To start the example with the local library run 28 | 29 | ```shell 30 | npm run start-local 31 | ``` 32 | 33 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 34 | 35 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 36 | -------------------------------------------------------------------------------- /src/hooks/use-map.ts: -------------------------------------------------------------------------------- 1 | import {useContext} from 'react'; 2 | 3 | import {APIProviderContext} from '../components/api-provider'; 4 | import {GoogleMapsContext} from '../components/map'; 5 | import {logErrorOnce} from '../libraries/errors'; 6 | 7 | /** 8 | * Retrieves a map-instance from the context. This is either an instance 9 | * identified by id or the parent map instance if no id is specified. 10 | * Returns null if neither can be found. 11 | */ 12 | export const useMap = (id: string | null = null): google.maps.Map | null => { 13 | const ctx = useContext(APIProviderContext); 14 | const {map} = useContext(GoogleMapsContext) || {}; 15 | 16 | if (ctx === null) { 17 | logErrorOnce( 18 | 'useMap(): failed to retrieve APIProviderContext. ' + 19 | 'Make sure that the component exists and that the ' + 20 | 'component you are calling `useMap()` from is a sibling of the ' + 21 | '.' 22 | ); 23 | 24 | return null; 25 | } 26 | 27 | const {mapInstances} = ctx; 28 | 29 | // if an id is specified, the corresponding map or null is returned 30 | if (id !== null) return mapInstances[id] || null; 31 | 32 | // otherwise, return the closest ancestor 33 | if (map) return map; 34 | 35 | // finally, return the default map instance 36 | return mapInstances['default'] || null; 37 | }; 38 | -------------------------------------------------------------------------------- /examples/homepage-header/README.md: -------------------------------------------------------------------------------- 1 | # Homepage Header 2 | 3 | This example is used as the header-image of the [homepage for this project] 4 | [rgm-home]. 5 | 6 | ## Google Maps Platform API key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | 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) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | [rgm-home]: https://visgl.github.io/react-google-maps 38 | -------------------------------------------------------------------------------- /examples/react-wrapper-migration/README.md: -------------------------------------------------------------------------------- 1 | # Migrating from @googlemaps/react-wrapper 2 | 3 | This is an example to show how code that was written using 4 | `@googlemaps/react-wrapper` can be ported to this library. 5 | 6 | ## Google Maps Platform API key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | 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) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/routes-api/README.md: -------------------------------------------------------------------------------- 1 | # Routes API Rendering Example 2 | 3 | This is an example to show how to retrieve a route from the new Routes API 4 | and visualize it with polylines and advanced markers. 5 | 6 | ## Google Maps Platform API key 7 | 8 | This example does not come with an API key. Running the examples locally 9 | requires a valid API key for the Google Maps Platform. 10 | See [the official documentation][get-api-key] on how to create and 11 | configure your own key. 12 | 13 | The API key has to be provided via an environment variable 14 | `GOOGLE_MAPS_API_KEY`. This can be done by creating a file named `.env` in 15 | the example directory with the following content: 16 | 17 | ```shell title=".env" 18 | GOOGLE_MAPS_API_KEY="" 19 | ``` 20 | 21 | If you are on the CodeSandbox playground you can also choose to 22 | [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 23 | 24 | ## Development 25 | 26 | Go into the example-directory and run 27 | 28 | ```shell 29 | npm install 30 | ``` 31 | 32 | To start the example with the local library run 33 | 34 | ```shell 35 | npm run start-local 36 | ``` 37 | 38 | The regular `npm start` task is only used for the standalone versions of 39 | the example (CodeSandbox for example) 40 | 41 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 42 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/README.md: -------------------------------------------------------------------------------- 1 | # Custom Marker Clustering Example 2 | 3 | This example demonstrates how the [Supercluster](https://github.com/mapbox/supercluster) 4 | algorithm can be directly used with `@vis.gl/react-google-maps` to create 5 | 6 | ## Google Maps API key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via the environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | 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) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/basic-map/README.md: -------------------------------------------------------------------------------- 1 | # Basic Map Setup Example 2 | 3 | ![image](https://user-images.githubusercontent.com/39244966/208682692-d5b23518-9e51-4a87-8121-29f71e41c777.png) 4 | 5 | This is an example to show how to setup a simple Google map with the `` component. 6 | 7 | ## Google Maps Platform API Key 8 | 9 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 10 | See [the official documentation][get-api-key] on how to create and configure your own key. 11 | 12 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 13 | file named `.env` in the example directory with the following content: 14 | 15 | ```shell title=".env" 16 | GOOGLE_MAPS_API_KEY="" 17 | ``` 18 | 19 | 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) 20 | 21 | ## Development 22 | 23 | Go into the example-directory and run 24 | 25 | ```shell 26 | npm install 27 | ``` 28 | 29 | To start the example with the local library run 30 | 31 | ```shell 32 | npm run start-local 33 | ``` 34 | 35 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 36 | 37 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 38 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | 5 | permissions: 6 | contents: write 7 | pull-requests: write 8 | 9 | name: Release Please 10 | 11 | jobs: 12 | release-please: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - id: release 16 | name: Release Please 17 | uses: googleapis/release-please-action@v4 18 | 19 | with: 20 | release-type: node 21 | package-name: '@vis.gl/react-google-maps' 22 | bump-minor-pre-major: true 23 | 24 | # Below are the actions for actual npm publishing when a release-branch was merged. 25 | 26 | - if: ${{ steps.release.outputs.release_created }} 27 | name: Checkout 28 | uses: actions/checkout@v4 29 | 30 | - if: ${{ steps.release.outputs.release_created }} 31 | name: Setup Node 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: 20 35 | cache: npm 36 | registry-url: 'https://registry.npmjs.org' 37 | 38 | - if: ${{ steps.release.outputs.release_created }} 39 | name: Install Dependencies 40 | run: npm ci 41 | 42 | - if: ${{ steps.release.outputs.release_created }} 43 | name: Publish 44 | # npm publish will trigger the build via the prepack hook 45 | run: npm publish 46 | env: 47 | NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} 48 | -------------------------------------------------------------------------------- /examples/map-3d/src/utility-hooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DependencyList, 3 | EffectCallback, 4 | Ref, 5 | useCallback, 6 | useEffect, 7 | useRef, 8 | useState 9 | } from 'react'; 10 | import isDeepEqual from 'fast-deep-equal'; 11 | 12 | export function useCallbackRef() { 13 | const [el, setEl] = useState(null); 14 | const ref = useCallback((value: T) => setEl(value), [setEl]); 15 | 16 | return [el, ref as Ref] as const; 17 | } 18 | 19 | export function useDeepCompareEffect( 20 | effect: EffectCallback, 21 | deps: DependencyList 22 | ) { 23 | const ref = useRef(undefined); 24 | 25 | if (!ref.current || !isDeepEqual(deps, ref.current)) { 26 | ref.current = deps; 27 | } 28 | 29 | // eslint-disable-next-line react-hooks/exhaustive-deps 30 | useEffect(effect, ref.current); 31 | } 32 | 33 | export function useDebouncedEffect( 34 | effect: EffectCallback, 35 | timeout: number, 36 | deps: DependencyList 37 | ) { 38 | const timerRef = useRef(0); 39 | 40 | useEffect( 41 | () => { 42 | if (timerRef.current) { 43 | clearTimeout(timerRef.current); 44 | timerRef.current = 0; 45 | } 46 | 47 | timerRef.current = setTimeout(() => effect(), timeout); 48 | return () => clearTimeout(timerRef.current); 49 | }, 50 | // eslint-disable-next-line react-hooks/exhaustive-deps 51 | [timeout, ...deps] 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /examples/map-3d/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const GMP_3D_MAPS_OVERVIEW_URL = 4 | 'https://developers.google.com/maps/documentation/javascript/3d-maps-overview'; 5 | 6 | function ControlPanel() { 7 | return ( 8 |
9 |

3D Maps

10 |

11 | This example implements a new Map3D component that renders 12 | a 3D Globe based on the new{' '} 13 | 14 | Map3DElement 15 | {' '} 16 | web-component. 17 |

18 | 19 |

20 | The mini-map in the bottom right shows the position and heading of the 21 | globe camera as well as the center-point of the globe view. Clicking in 22 | the mini-map will update the center-point of the globe. 23 |

24 | 25 | 38 |
39 | ); 40 | } 41 | 42 | export default React.memo(ControlPanel); 43 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/README.md: -------------------------------------------------------------------------------- 1 | # Advanced Marker interaction example 2 | 3 | This example showcases a classic interaction pattern when dealing with map markers. 4 | It covers hover-, click- and z-index handling as well as modifying the anchor point for an `AdvancedMarker`. 5 | 6 | ## Google Maps Platform API Key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | 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) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/places-ui-kit-3d/src/utility-hooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DependencyList, 3 | EffectCallback, 4 | Ref, 5 | useCallback, 6 | useEffect, 7 | useRef, 8 | useState 9 | } from 'react'; 10 | import isDeepEqual from 'fast-deep-equal'; 11 | 12 | export function useCallbackRef() { 13 | const [el, setEl] = useState(null); 14 | const ref = useCallback((value: T) => setEl(value), [setEl]); 15 | 16 | return [el, ref as Ref] as const; 17 | } 18 | 19 | export function useDeepCompareEffect( 20 | effect: EffectCallback, 21 | deps: DependencyList 22 | ) { 23 | const ref = useRef(undefined); 24 | 25 | if (!ref.current || !isDeepEqual(deps, ref.current)) { 26 | ref.current = deps; 27 | } 28 | 29 | // eslint-disable-next-line react-hooks/exhaustive-deps 30 | useEffect(effect, ref.current); 31 | } 32 | 33 | export function useDebouncedEffect( 34 | effect: EffectCallback, 35 | timeout: number, 36 | deps: DependencyList 37 | ) { 38 | const timerRef = useRef(0); 39 | 40 | useEffect( 41 | () => { 42 | if (timerRef.current) { 43 | clearTimeout(timerRef.current); 44 | timerRef.current = 0; 45 | } 46 | 47 | timerRef.current = setTimeout(() => effect(), timeout); 48 | return () => clearTimeout(timerRef.current); 49 | }, 50 | // eslint-disable-next-line react-hooks/exhaustive-deps 51 | [timeout, ...deps] 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /examples/advanced-marker/src/style.css: -------------------------------------------------------------------------------- 1 | /* ---- fonts ---- */ 2 | @import url('https://fonts.googleapis.com/css?family=Rufina:wght@400;700|Raleway:wght@0,100,700|Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0'); 3 | 4 | :root { 5 | /* real estate */ 6 | --estate-yellow: #b88b2e; 7 | --estate-yellow-light: #ffe29c; 8 | --estate-green-dark: #12271e; 9 | --estate-green-light: #18342a; 10 | --estate-01-grey-mid: #f4f4f4; 11 | --estate-grey-main: #404040; 12 | /* basic colors */ 13 | --background-color-primary: #fff; 14 | /* fonts */ 15 | --font-family-rufina: 'Rufina', serif; 16 | --font-family-raleway: 'Raleway', sans-serif; 17 | } 18 | 19 | .advanced-marker-example * { 20 | box-sizing: border-box; 21 | } 22 | 23 | .advanced-marker-example { 24 | width: 100%; 25 | height: 100%; 26 | } 27 | 28 | .advanced-marker-example *:before, 29 | .advanced-marker-example *:after { 30 | box-sizing: border-box; 31 | } 32 | 33 | .advanced-marker-example .material-symbols-outlined { 34 | font-family: 'Material Symbols Outlined'; 35 | font-weight: normal; 36 | font-style: normal; 37 | font-size: 24px; 38 | line-height: 1; 39 | letter-spacing: normal; 40 | text-transform: none; 41 | display: inline-block; 42 | white-space: nowrap; 43 | word-wrap: normal; 44 | direction: ltr; 45 | font-feature-settings: 'liga'; 46 | -webkit-font-feature-settings: 'liga'; 47 | -webkit-font-smoothing: antialiased; 48 | } 49 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/src/components/features-cluster-marker.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import { 3 | AdvancedMarker, 4 | AdvancedMarkerAnchorPoint, 5 | useAdvancedMarkerRef 6 | } from '@vis.gl/react-google-maps'; 7 | import {CastleSvg} from './castle-svg'; 8 | 9 | type TreeClusterMarkerProps = { 10 | clusterId: number; 11 | onMarkerClick?: ( 12 | marker: google.maps.marker.AdvancedMarkerElement, 13 | clusterId: number 14 | ) => void; 15 | position: google.maps.LatLngLiteral; 16 | size: number; 17 | sizeAsText: string; 18 | }; 19 | 20 | export const FeaturesClusterMarker = ({ 21 | position, 22 | size, 23 | sizeAsText, 24 | onMarkerClick, 25 | clusterId 26 | }: TreeClusterMarkerProps) => { 27 | const [markerRef, marker] = useAdvancedMarkerRef(); 28 | const handleClick = useCallback( 29 | () => onMarkerClick && onMarkerClick(marker!, clusterId), 30 | [onMarkerClick, marker, clusterId] 31 | ); 32 | const markerSize = Math.floor(48 + Math.sqrt(size) * 2); 33 | return ( 34 | 42 | 43 | {sizeAsText} 44 | 45 | ); 46 | }; 47 | --------------------------------------------------------------------------------