├── .nvmrc ├── docs ├── .nvmrc ├── public │ ├── .nojekyll │ ├── favicon.ico │ ├── marker-pin.png │ ├── marker-pin-draggable.png │ ├── manifest.json │ ├── vercel.svg │ └── next.svg ├── eslint.config.js ├── .env.prod ├── src │ ├── components │ │ ├── coordinates.ts │ │ ├── marker.tsx │ │ ├── info.tsx │ │ └── map-options.json │ └── app │ │ ├── layout.tsx │ │ ├── globals.css │ │ ├── page.module.css │ │ └── page.tsx ├── .gitignore ├── tsconfig.json ├── package.json ├── README.md └── next.config.js ├── .commitlintrc.json ├── .husky ├── pre-commit ├── commit-msg └── common.sh ├── src ├── index.ts ├── utils │ ├── utils.ts │ └── types.ts ├── map │ ├── hooks │ │ ├── useGoogleMaps.ts │ │ ├── useMemoCompare.ts │ │ └── useScript.ts │ ├── markers.tsx │ ├── overlay-view.tsx │ ├── map.tsx │ └── overlay.tsx └── google-map.tsx ├── .prettierignore ├── CONTRIBUTING.md ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 2.feature_request.yml │ └── 1.bug_report.yml ├── dependabot.yml └── workflows │ ├── STALE_ISSUE.yml │ └── deploy.yml ├── .babelrc.json ├── .prettierrc.json ├── tsup.config.ts ├── .gitignore ├── tsconfig.json ├── LICENCE.md ├── eslint.config.js ├── package.json ├── dist ├── index.js ├── index.cjs ├── index.d.cts └── index.d.ts └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v20 -------------------------------------------------------------------------------- /docs/.nvmrc: -------------------------------------------------------------------------------- 1 | v20 -------------------------------------------------------------------------------- /docs/public/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/common.sh" 3 | 4 | yarn lint-staged -------------------------------------------------------------------------------- /docs/eslint.config.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | extends: 'next/core-web-vitals', 4 | }, 5 | ] 6 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giorgiabosello/google-maps-react-markers/HEAD/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/marker-pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giorgiabosello/google-maps-react-markers/HEAD/docs/public/marker-pin.png -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import GoogleMap from './google-map' 2 | 3 | export * from './utils/types' 4 | 5 | export default GoogleMap 6 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | . "$(dirname "$0")/common.sh" 4 | 5 | yarn commitlint --edit $1 -------------------------------------------------------------------------------- /docs/.env.prod: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_MEDIA_URL=/google-maps-react-markers 2 | NEXT_PUBLIC_GOOGLE_MAPS_API_KEY='AIzaSyDDu4eepAbq9gEMb7Dx3LBXFw9rwAQE_4M' -------------------------------------------------------------------------------- /docs/public/marker-pin-draggable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giorgiabosello/google-maps-react-markers/HEAD/docs/public/marker-pin-draggable.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | .prettierignore 4 | yarn.lock 5 | *.snap 6 | .npmrc 7 | .husky 8 | .gitignore 9 | .editorconfig 10 | .env.template -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | **Working on your first Pull Request?** You can learn how from this _free_ series [How to Contribute to an Open Source Project on GitHub](https://kcd.im/pull-request) -------------------------------------------------------------------------------- /.husky/common.sh: -------------------------------------------------------------------------------- 1 | command_exists () { 2 | command -v "$1" >/dev/null 2>&1 3 | } 4 | 5 | # Workaround for Windows 10, Git Bash and Yarn 6 | if command_exists winpty && test -t 1; then 7 | exec < /dev/tty 8 | fi -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/giorgiabosello/google-maps-react-markers/discussions 5 | about: Ask questions and discuss with other community members 6 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "unambiguous", 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "chrome": 100 9 | } 10 | } 11 | ], 12 | "@babel/preset-typescript", 13 | "@babel/preset-react" 14 | ], 15 | "plugins": [] 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | export const isArraysEqualEps = (arrayA: number[], arrayB: number[], eps = 0.000001): boolean => { 2 | if (arrayA.length && arrayB.length) { 3 | for (let i = 0; i !== arrayA.length; ++i) { 4 | if (Math.abs(arrayA[i] - arrayB[i]) > eps) { 5 | return false 6 | } 7 | } 8 | return true 9 | } 10 | return false 11 | } 12 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "semi": false, 5 | "useTabs": true, 6 | "overrides": [ 7 | { 8 | "files": "*.json", 9 | "options": { 10 | "printWidth": 200 11 | } 12 | }, 13 | { 14 | "files": "*.md", 15 | "options": { 16 | "useTabs": false, 17 | "semi": false 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /docs/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "google-maps-react-markers", 3 | "name": "google-maps-react-markers", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /docs/src/components/coordinates.ts: -------------------------------------------------------------------------------- 1 | const coordinates = [ 2 | { 3 | lat: 45.4046987, 4 | lng: 12.2472504, 5 | name: 'Store draggable', 6 | }, 7 | ...[...Array(300)].map((_, i) => ({ 8 | lat: parseFloat((Math.random() * 180 - 90).toFixed(6)), 9 | lng: parseFloat((Math.random() * 360 - 180).toFixed(6)), 10 | name: `Store #${i + 1}`, 11 | })), 12 | ] 13 | 14 | export default coordinates 15 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | 3 | const config: Options = { 4 | entry: ['src/index.ts'], 5 | treeshake: true, 6 | sourcemap: false, // process.env.NODE_ENV === 'development', 7 | minify: true, 8 | clean: true, 9 | dts: true, 10 | splitting: false, 11 | format: ['cjs', 'esm'], 12 | external: ['react'], 13 | injectStyle: false, 14 | } 15 | 16 | export default config 17 | -------------------------------------------------------------------------------- /docs/.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.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: 'npm' # See documentation for possible values 9 | directory: '/' # Location of package manifests 10 | schedule: 11 | interval: 'weekly' 12 | -------------------------------------------------------------------------------- /.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.js 7 | 8 | # JetBrains IDE files 9 | .idea/ 10 | 11 | # testing 12 | /coverage 13 | 14 | # production 15 | # /dist 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 20 | tsconfig.tsbuildinfo 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | #docs 28 | /docs/node_modules 29 | /docs/build 30 | 31 | #docs nextjs 32 | /docs/nextjs/node_modules 33 | /docs/nextjs/.next 34 | /docs/nextjs/next-env.d.ts -------------------------------------------------------------------------------- /docs/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "esModuleInterop": true, 5 | "jsx": "react-jsx", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "noEmit": true, 11 | // Types should go into this directory. 12 | // Removing this would place the .d.ts files 13 | // next to the .js files 14 | "outDir": "dist", 15 | // Generate d.ts files 16 | "declaration": true, 17 | // go to js file when using IDE functions like 18 | // "Go to Definition" in VSCode 19 | "declarationMap": true 20 | }, 21 | "exclude": ["node_modules", "build"] 22 | } 23 | -------------------------------------------------------------------------------- /docs/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import React from 'react' 4 | import './globals.css' 5 | 6 | const inter = Inter({ subsets: ['latin'] }) 7 | 8 | export const metadata: Metadata = { 9 | title: 'Google Maps React Markers', 10 | description: 11 | 'Google Maps library that accepts markers as react components, works with React 18+ and it is fully typed.', 12 | } 13 | 14 | export default function RootLayout({ children }: { children: React.ReactNode }) { 15 | return ( 16 | 17 |
{children} 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 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": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/STALE_ISSUE.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v5 14 | with: 15 | days-before-issue-stale: 30 16 | days-before-issue-close: 14 17 | stale-issue-label: 'stale' 18 | exempt-issue-labels: 'feature request' 19 | exempt-pr-labels: 'feature request' 20 | stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity.' 21 | close-issue-message: 'This issue was closed because it has been inactive for 14 days since being marked as stale.' 22 | days-before-pr-stale: -1 23 | days-before-pr-close: -1 24 | repo-token: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-maps-react-markers-nextjs-example", 3 | "homepage": ".", 4 | "version": "0.0.0", 5 | "private": true, 6 | "scripts": { 7 | "dev": "next dev -H google-maps-react-markers.com.tech", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "engines": { 13 | "node": ">=18.17.0" 14 | }, 15 | "dependencies": { 16 | "google-maps-react-markers": "link:..", 17 | "next": "15.4.7", 18 | "react": "link:../node_modules/react", 19 | "react-dom": "link:../node_modules/react-dom" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "link:../node_modules/@types/node", 23 | "@types/react": "link:../node_modules/@types/react", 24 | "@types/react-dom": "link:../node_modules/@types/react-dom", 25 | "eslint": "link:../node_modules/eslint", 26 | "eslint-config-next": "15.1.6", 27 | "typescript": "link:../node_modules/typescript" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | -------------------------------------------------------------------------------- /docs/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | protocol: 'https', 7 | hostname: 'img.shields.io', 8 | // port: '', 9 | // pathname: '', 10 | }, 11 | ], 12 | /** 13 | * Disable server-based image optimization. Next.js does not support 14 | * dynamic features with static exports. 15 | * 16 | * @see https://nextjs.org/docs/pages/api-reference/components/image#unoptimized 17 | */ 18 | unoptimized: true, 19 | }, 20 | /** 21 | * Enable static exports for the App Router. 22 | * 23 | * @see https://nextjs.org/docs/pages/building-your-application/deploying/static-exports 24 | */ 25 | output: process.env.NODE_ENV === 'development' ? 'standalone' : 'export', 26 | /** 27 | * Set base path. This is usually the slug of your repository. 28 | * 29 | * @see https://nextjs.org/docs/app/api-reference/next-config-js/basePath 30 | */ 31 | basePath: process.env.NODE_ENV === 'development' ? '' : '/google-maps-react-markers', 32 | } 33 | 34 | module.exports = nextConfig 35 | -------------------------------------------------------------------------------- /src/map/hooks/useGoogleMaps.ts: -------------------------------------------------------------------------------- 1 | import { IUseGoogleMaps, UseScriptStatus } from '../../utils/types' 2 | import useScript from './useScript' 3 | 4 | /** 5 | * @returns {"idle" | "loading" | "ready" | "error"} status 6 | */ 7 | export const useGoogleMaps = ({ 8 | apiKey, 9 | libraries = [], 10 | loadScriptExternally = false, 11 | status = 'idle', 12 | externalApiParams, 13 | callback, 14 | }: IUseGoogleMaps): UseScriptStatus => { 15 | if (typeof window !== 'undefined') window.googleMapsCallback = callback 16 | const apiParams = new URLSearchParams(externalApiParams)?.toString() 17 | const script = apiKey 18 | ? { 19 | src: `https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=googleMapsCallback&libraries=${libraries?.join( 20 | ',', 21 | )}${apiParams ? `&${apiParams}` : ''}`, 22 | attributes: { id: 'googleMapsApi' }, 23 | } 24 | : { 25 | src: `https://maps.googleapis.com/maps/api/js?callback=googleMapsCallback&libraries=${libraries?.join(',')}`, 26 | attributes: { id: 'googleMapsApi' }, 27 | } 28 | 29 | return useScript(script, loadScriptExternally ? status : undefined) 30 | } 31 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Giorgia Bosello 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. -------------------------------------------------------------------------------- /src/map/hooks/useMemoCompare.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | /** 4 | * A hook that compares the previous and current values of a reference. 5 | * @param {any} next - the current value of the reference 6 | * @param {function} compare - a function that compares the previous and current values 7 | * @returns {any} the previous value of the reference 8 | * @ref https://usehooks.com/useMemoCompare/ 9 | */ 10 | const useMemoCompare = (next: any, compare: Function): any => { 11 | // Ref for storing previous value 12 | const previousRef = useRef() 13 | const previous = previousRef.current 14 | // Pass previous and next value to compare function 15 | // to determine whether to consider them equal. 16 | const isEqual = compare(previous, next) 17 | // If not equal update previousRef to next value. 18 | // We only update if not equal so that this hook continues to return 19 | // the same old value if compare keeps returning true. 20 | useEffect(() => { 21 | if (!isEqual) { 22 | previousRef.current = next 23 | } 24 | }) 25 | // Finally, if equal then return the previous value 26 | return isEqual ? previous : next 27 | } 28 | 29 | export default useMemoCompare 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2.feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Create a bug report for Google Maps React Markers 3 | labels: ['template: enhancement'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: Thanks for taking the time to file a feature request! Please fill out this form as completely as possible. 8 | - type: markdown 9 | attributes: 10 | value: 'Feature requests will be converted to the GitHub Discussions "Ideas" section.' 11 | - type: textarea 12 | attributes: 13 | label: Describe the feature you'd like to request 14 | description: A clear and concise description of what you want and what your use case is. 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Describe the solution you'd like 20 | description: A clear and concise description of what you want to happen. 21 | validations: 22 | required: true 23 | - type: textarea 24 | attributes: 25 | label: Describe alternatives you've considered 26 | description: A clear and concise description of any alternative solutions or features you've considered. 27 | validations: 28 | required: true 29 | -------------------------------------------------------------------------------- /docs/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/google-map.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react' 2 | import { useGoogleMaps } from './map/hooks/useGoogleMaps' 3 | import MapComponent from './map/map' 4 | import { GoogleMapProps } from './utils/types' 5 | 6 | const GoogleMap = forwardRef{`${name} 👉 { lat: ${lat},lng: ${lng} }`}
22 |28 | Drag the yellow marker to see its coordinates 29 | change. 30 |
31 | {drag?.dragStart && drag?.dragEnd && ( 32 | <> 33 |Drag start:
34 |{drag.dragStart ? `lat: ${drag.dragStart.lat}, lng: ${drag.dragStart.lng}` : 'null'}
35 |Dragging:
36 |{drag.dragging ? `lat: ${drag.dragging.lat}, lng: ${drag.dragging.lng}` : 'null'}
37 |Drag end:
38 |{drag.dragEnd ? `lat: ${drag.dragEnd.lat}, lng: ${drag.dragEnd.lng}` : 'null'}
39 | > 40 | )} 41 |Map bounds are used to calculate clusters.
47 |Move the map to see the bounds change.
48 |{`${name}: ${bound}`}
53 | })} 54 |