├── .changeset ├── README.md └── config.json ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── deploy-next-website.yml │ ├── deploy-website.yml │ └── release.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── README.md ├── package.json ├── packages ├── next-docz │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── playground.tsx │ │ │ └── props.tsx │ │ ├── config.ts │ │ ├── index.ts │ │ ├── rehype │ │ │ ├── format.ts │ │ │ ├── index.ts │ │ │ ├── rehype.ts │ │ │ └── strip-indent.ts │ │ └── with-docz.ts │ ├── tsconfig.json │ └── tsup.config.ts └── react-naver-maps │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── container.tsx │ ├── contexts │ │ ├── client-options.ts │ │ ├── container.ts │ │ ├── event-target.ts │ │ └── naver-map.ts │ ├── deprecated.tsx │ ├── helpers │ │ └── event.tsx │ ├── hooks │ │ └── use-previous.ts │ ├── index.ts │ ├── listener.tsx │ ├── load-navermaps-script.tsx │ ├── naver-map.tsx │ ├── overlay.spec.tsx │ ├── overlay.tsx │ ├── overlays │ │ ├── circle.tsx │ │ ├── ellipse.tsx │ │ ├── ground-overlay.tsx │ │ ├── info-window.tsx │ │ ├── marker.spec.tsx │ │ ├── marker.tsx │ │ ├── polygon.tsx │ │ ├── polyline.tsx │ │ └── rectangle.tsx │ ├── provider.tsx │ ├── types │ │ ├── case.ts │ │ ├── client.ts │ │ ├── event.ts │ │ └── utils.ts │ ├── use-navermaps.spec.tsx │ ├── use-navermaps.ts │ └── utils │ │ ├── get-keys.ts │ │ ├── load-script.ts │ │ ├── omit-undefined.ts │ │ └── uncontrolled.ts │ ├── test │ ├── naver-map.test.tsx │ └── setupTests.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── website ├── .eslintrc.js ├── .gitignore ├── CHANGELOG.md ├── README.md ├── additional.d.ts ├── next-env.d.ts ├── next.config.mjs ├── package.json ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── google8aa2ebba48557029.html ├── mstile-150x150.png ├── safari-pinned-tab.svg └── site.webmanifest ├── src ├── components │ └── layout.tsx ├── hooks │ └── useIsDarkMode.tsx ├── lib │ └── gtag.ts ├── menu.ts ├── pages │ ├── _app.tsx │ ├── api-references │ │ ├── circle.mdx │ │ ├── container.mdx │ │ ├── ellipse.mdx │ │ ├── ground-overlay.mdx │ │ ├── info-window.mdx │ │ ├── listener.mdx │ │ ├── load-navermaps-script.mdx │ │ ├── marker.mdx │ │ ├── naver-map.mdx │ │ ├── navermaps-provider.mdx │ │ ├── ploygon.mdx │ │ ├── polygon.mdx │ │ ├── polyline.mdx │ │ ├── rectangle.mdx │ │ ├── use-listener.mdx │ │ ├── use-map.mdx │ │ └── use-navermaps.mdx │ ├── examples │ │ ├── control-tutorial-4-custom-p1.mdx │ │ ├── map-tutorial-1-simple.mdx │ │ ├── map-tutorial-2-options.mdx │ │ ├── map-tutorial-3-types.mdx │ │ ├── map-tutorial-4-bounds.mdx │ │ ├── map-tutorial-5-moves.mdx │ │ ├── map-tutorial-6-geolocation.mdx │ │ ├── marker-cluster-tutorial.mdx │ │ └── marker-tutorial-1-simple.mdx │ ├── guides │ │ ├── controlled-tutorial.mdx │ │ ├── core-concepts.mdx │ │ ├── customize-overlays.mdx │ │ ├── for-performance.mdx │ │ ├── installation.mdx │ │ ├── introduction.mdx │ │ ├── migration-guide-from-0.0.mdx │ │ ├── quickstart.mdx │ │ ├── suspensed-use-navermaps.mdx │ │ └── without-component.mdx │ └── index.tsx └── samples │ ├── accident-death.js │ └── marker-cluster.js └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist/** 2 | **/node_modules/** 3 | **/.next/** 4 | **/samples/*.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:react/recommended', 6 | 'plugin:react/jsx-runtime', 7 | 'plugin:@typescript-eslint/recommended', 8 | 'plugin:import/recommended', 9 | 'plugin:import/typescript', 10 | ], 11 | plugins: ['import'], 12 | parserOptions: { 13 | ecmaVersion: 2020, 14 | sourceType: 'module', 15 | ecmaFeatures: { jsx: true }, 16 | }, 17 | env: { 18 | es6: true, 19 | node: true, 20 | }, 21 | rules: { 22 | 'import/order': [ 23 | 'error', { 24 | alphabetize: { 25 | order: 'asc', 26 | caseInsensitive: true, 27 | }, 28 | 'newlines-between': 'always', 29 | groups: ['builtin', 'external', ['parent', 'sibling', 'index']], 30 | }, 31 | ], 32 | 'quotes': ['error', 'single'], 33 | 'eol-last': ['error', 'always'], 34 | 'prefer-template': 'error', 35 | 'object-curly-spacing': ['error', 'always'], 36 | 'comma-spacing': ['error'], 37 | 'no-multi-spaces': ['error'], 38 | 'no-unexpected-multiline': ['error'], 39 | 'object-curly-newline': ['error', { multiline: true }], 40 | 'array-bracket-newline': ['error', { multiline: true }], 41 | 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 0 }], 42 | 'function-paren-newline': ['error', 'multiline'], 43 | 'no-trailing-spaces': 'error', 44 | 'react/prop-types': ['off'], 45 | '@typescript-eslint/explicit-function-return-type': 'off', 46 | '@typescript-eslint/member-delimiter-style': ['error'], 47 | '@typescript-eslint/type-annotation-spacing': ['error'], 48 | 49 | 'indent': 'off', 50 | '@typescript-eslint/indent': ['error', 2], 51 | 'semi': 'off', 52 | '@typescript-eslint/semi': ['error'], 53 | 'comma-dangle': 'off', 54 | '@typescript-eslint/comma-dangle': ['error', 'always-multiline'], 55 | 'no-empty-function': 'off', 56 | '@typescript-eslint/no-empty-function': ['off'], 57 | '@typescript-eslint/no-explicit-any': ['off'], 58 | 59 | 'react/no-unknown-property': ['error', { ignore: ['css'] }], 60 | }, 61 | settings: { 62 | react: { 'version': 'detect' }, 63 | 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'] }, 64 | }, 65 | }; 66 | -------------------------------------------------------------------------------- /.github/workflows/deploy-next-website.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Next Website 2 | run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 3 | on: 4 | push: 5 | branches: 6 | - next 7 | jobs: 8 | build-and-deploy: 9 | concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession. 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 🛎️ 13 | uses: actions/checkout@v3 14 | - uses: pnpm/action-setup@v2 15 | with: 16 | version: 7 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | cache: 'pnpm' 22 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 23 | run: | 24 | pnpm install 25 | pnpm -r run build 26 | env: 27 | WEBSITE_BASE_PATH: '/react-naver-maps/next' 28 | 29 | - name: Deploy 🚀 30 | uses: JamesIves/github-pages-deploy-action@v4 31 | with: 32 | folder: website/out 33 | target-folder: './next' 34 | clean: false 35 | 36 | # runs-on: ubuntu-latest 37 | # steps: 38 | # - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." 39 | # - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" 40 | # - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 41 | # - name: Check out repository code 42 | # uses: actions/checkout@v3 43 | # - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." 44 | # - run: echo "🖥️ The workflow is now ready to test your code on the runner." 45 | # - name: List files in the repository 46 | # run: | 47 | # ls ${{ github.workspace }} 48 | # - run: echo "🍏 This job's status is ${{ job.status }}." -------------------------------------------------------------------------------- /.github/workflows/deploy-website.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Website 2 | run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 3 | on: 4 | push: 5 | branches: 6 | - main 7 | env: 8 | NEXT_PUBLIC_GA_TRACKING_ID: G-51RCXPWJLZ 9 | NEXT_PUBLIC_WEBSITE_BASE_PATH: '/react-naver-maps' 10 | 11 | jobs: 12 | build-and-deploy: 13 | concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession. 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 🛎️ 17 | uses: actions/checkout@v3 18 | - uses: pnpm/action-setup@v2 19 | with: 20 | version: 7 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: 'pnpm' 26 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 27 | run: | 28 | pnpm install 29 | pnpm -r run build 30 | 31 | - name: Deploy 🚀 32 | uses: JamesIves/github-pages-deploy-action@v4 33 | with: 34 | folder: website/out 35 | clean: false -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v3 17 | 18 | - uses: pnpm/action-setup@v2 19 | with: 20 | version: 7 21 | 22 | - name: Setup Node.js 16.x 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 16.x 26 | 27 | - name: Install Dependencies 28 | run: pnpm install 29 | 30 | - name: Create Release Pull Request or Publish to npm 31 | id: changesets 32 | uses: changesets/action@v1 33 | # with: 34 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 35 | # publish: yarn release 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # dist 61 | dist/ 62 | 63 | # styleguidist 64 | styleguide/build/ 65 | styleguide/index.html 66 | 67 | # editor 68 | .vscode 69 | 70 | .DS_Store 71 | 72 | .idea -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm install 5 | git add . 6 | pnpm recursive run build 7 | pnpm recursive run lint 8 | pnpm recursive run test 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Naver Maps 2 | 3 | React Navermaps API integration for modern development. 4 | 5 | ## Welcome 6 | 7 | 완전히 새로운 API의 v0.1 이 배포되었습니다. [v0.1.0](https://github.com/zeakd/react-naver-maps/issues/65) 8 | 9 | [Website](https://zeakd.github.io/react-naver-maps) 10 | 11 | 이전 레거시 버전 (v0.0.13) 관련 문서는 [Legacy Website](https://zeakd.github.io/react-naver-maps/0.0.13)을 참고해주세요 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "scripts": { 5 | "prepare": "husky install", 6 | "test": "echo hi" 7 | }, 8 | "devDependencies": { 9 | "@changesets/cli": "^2.26.0", 10 | "@typescript-eslint/eslint-plugin": "^5.40.1", 11 | "@typescript-eslint/parser": "^5.40.1", 12 | "eslint": "^8.25.0", 13 | "eslint-import-resolver-typescript": "^3.5.2", 14 | "eslint-plugin-import": "^2.26.0", 15 | "eslint-plugin-react": "^7.31.10", 16 | "husky": "^8.0.0", 17 | "typescript": "^4.8.4" 18 | }, 19 | "packageManager": "pnpm@7.26.3" 20 | } 21 | -------------------------------------------------------------------------------- /packages/next-docz/README.md: -------------------------------------------------------------------------------- 1 | # Next Docz 2 | 3 | **next-docz** is non-official docz library for next.js heavily inspired by [Docz](https://www.docz.site/) -------------------------------------------------------------------------------- /packages/next-docz/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-docz", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | }, 13 | "./config": { 14 | "import": "./dist/config.mjs" 15 | } 16 | }, 17 | "scripts": { 18 | "build": "tsup", 19 | "dev": "pnpm build --watch", 20 | "lint": "eslint src", 21 | "prepack": "pnpm build" 22 | }, 23 | "dependencies": { 24 | "@babel/parser": "^7.20.15", 25 | "@emotion/react": "^11.10.4", 26 | "@mdx-js/loader": "^2.1.1", 27 | "@microsoft/tsdoc": "^0.14.2", 28 | "@next/mdx": "12.3.1", 29 | "esast-util-from-js": "^1.1.0", 30 | "estree-util-visit": "^1.2.0", 31 | "prettier": "^2.7.1", 32 | "react-docgen-typescript": "^2.2.2", 33 | "unist-util-source": "^4.0.1", 34 | "unist-util-visit": "^4.1.1" 35 | }, 36 | "peerDependencies": { 37 | "@mdx-js/react": "*", 38 | "react": "^17 || ^18", 39 | "react-dom": "^17 || ^18" 40 | }, 41 | "devDependencies": { 42 | "@mdx-js/react": "^2.1.5", 43 | "@types/node": "16.11.10", 44 | "@types/react": "^18.0.22", 45 | "next": "^12.3.1", 46 | "react": "^18.2.0", 47 | "react-dom": "^18.2.0", 48 | "tsup": "^6.3.0", 49 | "typescript": "^4.8.4", 50 | "webpack": "^5.74.0" 51 | } 52 | } -------------------------------------------------------------------------------- /packages/next-docz/src/components/playground.tsx: -------------------------------------------------------------------------------- 1 | import { useMDXComponents } from '@mdx-js/react'; 2 | import React from 'react'; 3 | 4 | type Props = { 5 | children?: React.ReactNode | React.ComponentType; 6 | __code?: string; 7 | codeHeader?: string; 8 | codeClassName?: string; 9 | }; 10 | 11 | export function Playground(props: Props) { 12 | const components = useMDXComponents(); 13 | const Code = components.code ?? 'code'; 14 | 15 | return ( 16 | <> 17 | {typeof props.children === 'function' ? React.createElement(props.children as React.FC) : props.children} 18 | 19 | {`${props.codeHeader || ''}\n${props.__code}`} 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/next-docz/src/components/props.tsx: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import type { ReactNode, ComponentType } from 'react'; 3 | import { Fragment } from 'react'; 4 | 5 | type Props = { 6 | of?: ComponentType; 7 | children?: ReactNode; 8 | attrs?: string[]; 9 | __docgen?: any; 10 | }; 11 | 12 | export function Props({ __docgen: docgen, attrs: attrNames }: Props) { 13 | const attrs = Object.entries(docgen.props).filter(([key]) => { 14 | if (attrNames) { 15 | return attrNames.includes(key); 16 | } 17 | 18 | return !['key', 'ref'].includes(key); 19 | }).map(([, value]) => value); 20 | 21 | return ( 22 |
33 |
Name
40 |
Type
47 |
Default
54 |
Description
61 | {attrs.map((attr: any) => { 62 | return ( 63 | 66 |
73 | {attr.name} 74 |
75 |
81 | {attr.type?.name} 82 |
83 |
86 | {attr.required ? ( 87 | Required 88 | ) : ( 89 | 92 | {attr.defaultValue != null ? attr.defaultValue.value : '-'} 93 | 94 | )} 95 |
96 |
102 | {attr.description} 103 |
104 |
105 | ); 106 | })} 107 |
108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /packages/next-docz/src/config.ts: -------------------------------------------------------------------------------- 1 | export { withDocz } from './with-docz'; 2 | -------------------------------------------------------------------------------- /packages/next-docz/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Playground } from './components/playground'; 2 | export { Props } from './components/props'; 3 | -------------------------------------------------------------------------------- /packages/next-docz/src/rehype/format.ts: -------------------------------------------------------------------------------- 1 | // import { prettier } from 'prettier/esm'; 2 | 3 | export const formatter = async (code: string) => { 4 | 5 | const { default: prettier } = await import('prettier'); 6 | return prettier.format(code, { 7 | parser: 'babel', 8 | semi: false, 9 | singleQuote: true, 10 | trailingComma: 'all', 11 | }); 12 | }; 13 | 14 | export const format = async (code: string): Promise => { 15 | try { 16 | const result = await formatter(code); 17 | return result; 18 | } catch (error) { 19 | console.error(error); 20 | return code; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /packages/next-docz/src/rehype/index.ts: -------------------------------------------------------------------------------- 1 | export { rehypeDocz } from './rehype'; 2 | -------------------------------------------------------------------------------- /packages/next-docz/src/rehype/rehype.ts: -------------------------------------------------------------------------------- 1 | 2 | import fs from 'fs'; 3 | import { createRequire } from 'module'; 4 | import path from 'path'; 5 | import { pathToFileURL } from 'url'; 6 | 7 | import { fromJs } from 'esast-util-from-js'; 8 | import { visit as estreeVisit } from 'estree-util-visit'; 9 | import docgen from 'react-docgen-typescript'; 10 | import { source } from 'unist-util-source'; 11 | import { visit } from 'unist-util-visit'; 12 | 13 | import { format } from './format'; 14 | import { strip } from './strip-indent'; 15 | 16 | const require = createRequire(pathToFileURL(path.resolve(process.cwd(), './next.config.mjs'))); 17 | 18 | function isPlayground(name: string) { 19 | return name.includes('Playground'); 20 | } 21 | 22 | function isProps(name: string) { 23 | return name.includes('Props'); 24 | } 25 | 26 | function walk(children: any[], cb: (node: any) => void) { 27 | for (const node of children) { 28 | cb(node); 29 | node.children && walk(node.children, cb); 30 | } 31 | } 32 | 33 | const REGEX_RENDER_FUNCTION = /^{\s*;\(\)\s*=>.*([\w\W]*)}\s*}$/; 34 | const REGEX_RETURN_STATEMENT = /return\s*((?:[\w\W](?!return))+)$/; 35 | const REGEX_PARENTHESIS = /\(([\w\W]*)\)\s*;?\s*/; 36 | 37 | const addComponentsProps = (vfile: any) => async (node: any, idx: number) => { 38 | if (isPlayground(node.name)) { 39 | const codes = await Promise.all(node.children 40 | .map((child: any) => format(source(child, vfile)!)) 41 | .map(async (promise: Promise) => { 42 | const str = await promise; 43 | if (str.startsWith(';')) return str.slice(1, Infinity); 44 | return str; 45 | })); 46 | let code = codes.join('').trim(); 47 | 48 | // remove return statement if code is render function instead of react element 49 | const match = code.match(REGEX_RENDER_FUNCTION); 50 | if (match) { 51 | code = strip(match[1]); 52 | 53 | const returnStatement = code.match(REGEX_RETURN_STATEMENT); 54 | if (returnStatement) { 55 | const haveParenthesis = returnStatement[1].match(REGEX_PARENTHESIS); 56 | const unwrapped = haveParenthesis ? haveParenthesis[1] : returnStatement[1]; 57 | 58 | code = code.replace( 59 | REGEX_RETURN_STATEMENT, 60 | strip(unwrapped).trim(), 61 | ); 62 | } 63 | } 64 | 65 | code = strip(code).trim(); 66 | 67 | const attrType = node.attributes.find((attr: any) => attr.name === 'type'); 68 | node.attributes.push( 69 | { type: 'mdxJsxAttribute', name: '__position', value: idx }, 70 | { type: 'mdxJsxAttribute', name: '__code', value: code }, 71 | ); 72 | 73 | walk(node.children, (node) => { 74 | if (node.type === 'mdxJsxFlowElement') { 75 | node.attributes.push({ 76 | type: 'mdxJsxAttribute', 77 | name: 'client:only', 78 | value: attrType?.value ?? 'react', 79 | }); 80 | } 81 | }); 82 | } 83 | }; 84 | 85 | export interface PluginOpts { 86 | root: string; 87 | } 88 | 89 | export function rehypeDocz() { 90 | return async (tree: any, vfile: any) => { 91 | const nodes = tree.children 92 | .filter((node: any) => node.type.toLowerCase().includes('jsx')) 93 | .map(addComponentsProps(vfile)); 94 | 95 | await Promise.all(nodes); 96 | 97 | visit(tree, (node: any) => { 98 | return node.type === 'mdxJsxFlowElement' && isProps(node.name); 99 | }, (targetNode) => { 100 | const ofAttr = targetNode.attributes?.find((attr: any) => attr.name === 'of'); 101 | const componentName = ofAttr.value.value; 102 | 103 | let sourceModuleName = ''; 104 | visit(tree, (node: any) => { 105 | return node.type === 'mdxjsEsm'; 106 | }, (node: any) => { 107 | estreeVisit(node.data.estree, (node: any) => { 108 | if (!node.specifiers) return; 109 | for (const specifier of node.specifiers) { 110 | if (specifier.imported.name === componentName) { 111 | sourceModuleName = node.source.value; 112 | } 113 | } 114 | }); 115 | }); 116 | 117 | // find module from sourcemap 118 | const filepath = require.resolve(sourceModuleName); 119 | const sourcemap = JSON.parse(fs.readFileSync(`${filepath}.map`, 'utf8')); 120 | 121 | const parsed = docgen.parse(path.resolve(path.dirname(filepath), sourcemap.sources[0])) as any; 122 | const doc = parsed.filter((comp: any) => comp.displayName === componentName)[0]; 123 | 124 | targetNode.attributes.push({ 125 | type: 'mdxJsxAttribute', 126 | name: '__docgen', 127 | value: { 128 | type: 'mdxJsxAttributeValueExpression', 129 | value: `(${JSON.stringify(doc)})`, 130 | data: { estree: fromJs(`(${JSON.stringify(doc)})`, { module: true }) }, 131 | }, 132 | }); 133 | }); 134 | 135 | return tree; 136 | }; 137 | } 138 | -------------------------------------------------------------------------------- /packages/next-docz/src/rehype/strip-indent.ts: -------------------------------------------------------------------------------- 1 | function minIndent(str: string) { 2 | const match = str.match(/^[ \t]*(?=\S)/gm); 3 | if (!match) { 4 | return 0; 5 | } 6 | return match.reduce((r, a) => Math.min(r, a.length), Infinity); 7 | } 8 | 9 | export function strip(str: string) { 10 | const indent = minIndent(str); 11 | if (indent === 0) { 12 | return str; 13 | } 14 | const regex = new RegExp(`^[ \\t]{${indent}}`, 'gm'); 15 | return str.replace(regex, ''); 16 | } 17 | -------------------------------------------------------------------------------- /packages/next-docz/src/with-docz.ts: -------------------------------------------------------------------------------- 1 | import withMDX from '@next/mdx'; 2 | import type { NextConfig } from 'next'; 3 | 4 | import { rehypeDocz } from './rehype'; 5 | 6 | export function withDocz(config: NextConfig) { 7 | return withMDX({ options: { rehypePlugins: [rehypeDocz] } })(config); 8 | } 9 | -------------------------------------------------------------------------------- /packages/next-docz/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | "jsx": "react-jsx", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "jsxImportSource": "@emotion/react", 14 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | "declarationDir": "./types", 16 | 17 | // "emitDeclarationOnly": true, 18 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 19 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 20 | // "outFile": "./", /* Concatenate and emit output to single file. */ 21 | // "outDir": "./dist", /* Redirect output structure to the directory. */ 22 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 23 | // "composite": true, /* Enable project compilation */ 24 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 25 | // "removeComments": true, /* Do not emit comments to output. */ 26 | // "noEmit": true, /* Do not emit outputs. */ 27 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 28 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 29 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 30 | 31 | /* Strict Type-Checking Options */ 32 | "strict": true, /* Enable all strict type-checking options. */ 33 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 34 | // "strictNullChecks": true, /* Enable strict null checks. */ 35 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 36 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 37 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 38 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 39 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 40 | 41 | /* Additional Checks */ 42 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 43 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 44 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 45 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 46 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 47 | 48 | /* Module Resolution Options */ 49 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 50 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 51 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 52 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 53 | // "typeRoots": [], /* List of folders to include type definitions from. */ 54 | "types": [], /* Type declaration files to be included in compilation. */ 55 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 56 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 57 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 58 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 59 | 60 | /* Source Map Options */ 61 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 62 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 63 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 64 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 65 | 66 | /* Experimental Options */ 67 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 68 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 69 | 70 | /* Advanced Options */ 71 | // "skipLibCheck": true, /* Skip type checking of declaration files. */ 72 | // "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 73 | "importsNotUsedAsValues": "error" 74 | }, 75 | "include": ["src", "test"], 76 | "exclude": ["node_modules"], 77 | } 78 | -------------------------------------------------------------------------------- /packages/next-docz/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | import pkgJson from './package.json'; 4 | 5 | const external = [...Object.keys(pkgJson.dependencies || {}), ...Object.keys(pkgJson.peerDependencies || {})]; 6 | 7 | export default defineConfig({ 8 | entry: ['src/**/*.ts?(x)', '!src/**/*.(spec|test).ts?(x)'], 9 | format: ['esm', 'cjs'], 10 | external, 11 | clean: true, 12 | dts: true, 13 | }); 14 | -------------------------------------------------------------------------------- /packages/react-naver-maps/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # react-naver-maps 2 | 3 | ## 0.1.4 4 | 5 | ### Patch Changes 6 | 7 | - b8ae0a7: 신규 client key 이름 ncpKeyId를 사용가능하도록 업데이트 8 | 9 | ## 0.1.3 10 | 11 | ### Patch Changes 12 | 13 | - 00ff206: update naver host openapi -> oapi 14 | 15 | ## 0.1.2 16 | 17 | ### Patch Changes 18 | 19 | - update package.json 20 | 21 | ## 0.1.1 22 | 23 | ### Patch Changes 24 | 25 | - deprecated alert을 위한 RenderAfterNavermapsLoaded 추가 26 | 27 | ## 0.1.0 28 | 29 | ### Major Changes 30 | 31 | - 50c7d39: release v0.1.0 32 | - 894ccb1: change react support version, use jsx-runtime 33 | 34 | ## 0.1.0-next.2 35 | 36 | ### Minor Changes 37 | 38 | - change react support version, use jsx-runtime 39 | 40 | ## 0.1.0-next.1 41 | 42 | ### Patch Changes 43 | 44 | - Update peerdeps 45 | 46 | ## 0.1.0-next.0 47 | 48 | ### Minor Changes 49 | 50 | - release v0.1.0 51 | -------------------------------------------------------------------------------- /packages/react-naver-maps/README.md: -------------------------------------------------------------------------------- 1 | # React Naver Maps 2 | 3 | React Navermaps API integration for modern development. 4 | 5 | ## Welcome 6 | 7 | 완전히 새로운 API의 v0.1 이 배포되었습니다. [v0.1.0](https://github.com/zeakd/react-naver-maps/issues/65) 8 | 9 | [Website](https://zeakd.github.io/react-naver-maps) 10 | 11 | 이전 레거시 버전 (v0.0.13) 관련 문서는 [Legacy Website](https://zeakd.github.io/react-naver-maps/0.0.13)을 참고해주세요 12 | 13 | -------------------------------------------------------------------------------- /packages/react-naver-maps/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | verbose: true, 4 | preset: 'ts-jest', 5 | testEnvironment: 'jsdom', 6 | testMatch: [ 7 | '/test/**/?(*.)+(spec|test).[t]s?(x)', 8 | '**/?(*.)+(spec|test).[t]s?(x)', 9 | ], 10 | setupFilesAfterEnv: ['./test/setupTests.ts'], 11 | }; 12 | -------------------------------------------------------------------------------- /packages/react-naver-maps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-naver-maps", 3 | "version": "0.1.4", 4 | "description": "React Navermaps API integration for modern development.", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "types": "./dist/index.d.ts", 11 | "import": "./dist/index.mjs", 12 | "module": "./dist/index.mjs", 13 | "require": "./dist/index.js", 14 | "default": "./dist/index.js" 15 | } 16 | }, 17 | "scripts": { 18 | "dev": "tsup --watch", 19 | "build": "tsup --clean", 20 | "prepack": "pnpm build", 21 | "lint": "eslint src", 22 | "test": "jest" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/zeakd/react-naver-maps.git" 27 | }, 28 | "keywords": [ 29 | "react", 30 | "naver", 31 | "map", 32 | "navermaps", 33 | "navermap" 34 | ], 35 | "author": "zeakd ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/zeakd/react-naver-maps/issues" 39 | }, 40 | "homepage": "https://zeakd.github.io/react-naver-maps", 41 | "dependencies": { 42 | "camelcase": "^5.3.1", 43 | "load-script": "^2.0.0", 44 | "lodash.isempty": "^4.4.0", 45 | "lodash.mapkeys": "^4.6.0", 46 | "lodash.omit": "^4.5.0", 47 | "lodash.pick": "^4.4.0", 48 | "lodash.upperfirst": "^4.3.1", 49 | "prop-types": "^15.7.2", 50 | "react-use": "^17.3.1", 51 | "suspend-react": "^0.0.8" 52 | }, 53 | "peerDependencies": { 54 | "react": "^17.0.0 || ^18.0.0", 55 | "react-dom": "^17.0.0 || ^18.0.0" 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.16.0", 59 | "@babel/preset-env": "^7.16.4", 60 | "@testing-library/react": "^13.4.0", 61 | "@types/jest": "^29.2.0", 62 | "@types/lodash.isempty": "^4.4.6", 63 | "@types/lodash.mapkeys": "^4.6.7", 64 | "@types/lodash.omit": "^4.5.7", 65 | "@types/lodash.pick": "^4.4.6", 66 | "@types/lodash.upperfirst": "^4.3.7", 67 | "@types/navermaps": "^3.0.13", 68 | "@types/react": "^18", 69 | "jest": "^29.2.0", 70 | "jest-environment-jsdom": "^29.2.0", 71 | "react": "^18.2.0", 72 | "react-dom": "^18.2.0", 73 | "trash-cli": "^4.0.0", 74 | "ts-jest": "^29.0.3", 75 | "tslib": "^2.4.0", 76 | "tsup": "^6.3.0", 77 | "typescript": "^4.8.4" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/container.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense, useEffect, useMemo, useRef, useState, createElement } from 'react'; 2 | import type { ReactNode, ComponentPropsWithoutRef, CSSProperties, ComponentType } from 'react'; 3 | 4 | import { ContainerContext, ContainerContextType } from './contexts/container'; 5 | 6 | export type Props = { 7 | innerStyle?: CSSProperties; 8 | fallback?: ReactNode; 9 | /** 10 | * 일반 children 혹은 render function 11 | */ 12 | children?: ReactNode | ComponentType; 13 | } & Omit, 'children'>; 14 | 15 | const innerDefaultStyle: CSSProperties = { top: 0, left: 0, width: '100%', height: '100%', position: 'absolute', zIndex: 0 }; 16 | 17 | export function Container({ children, fallback, innerStyle = innerDefaultStyle, ...restProps }: Props) { 18 | const ref = useRef(null); 19 | const [isMounted, setIsMounted] = useState(false); 20 | 21 | useEffect(() => { 22 | setIsMounted(true); 23 | }, []); 24 | 25 | const containerContext = useMemo(() => ({ element: ref.current }), [ref.current]); 26 | 27 | return ( 28 |
29 |
30 | {isMounted && ref.current ? ( 31 | 32 | 33 | {typeof children === 'function' ? createElement(children as ComponentType) : children} 34 | 35 | 36 | ) : fallback} 37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/contexts/client-options.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | import type { ClientOptions } from '../types/client'; 4 | 5 | export const ClientOptionsContext = createContext({} as ClientOptions); 6 | export const useClientOptions = () => useContext(ClientOptionsContext); 7 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/contexts/container.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | export type ContainerContextType = { element: HTMLElement | null }; 4 | 5 | export const ContainerContext = createContext({ element: null }); 6 | export const useContainerContext = () => useContext(ContainerContext); 7 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/contexts/event-target.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | export const EventTargetContext = createContext(undefined); 4 | export const useEventTarget: () => any | undefined = () => useContext(EventTargetContext); 5 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/contexts/naver-map.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | export const NaverMapContext = createContext(undefined); 4 | export const useMap: () => naver.maps.Map | undefined = () => useContext(NaverMapContext); 5 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/deprecated.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | */ 4 | export function RenderAfterNavermapsLoaded() { 5 | throw new Error('react-naver-maps: v0.1 부터 는 더이상 사용되지 않습니다. 마이그레이션 가이드를 확인해주세요. https://zeakd.github.io/react-naver-maps/guides/migration-guide-from-0.0'); 6 | } 7 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/helpers/event.tsx: -------------------------------------------------------------------------------- 1 | 2 | import camelcase from 'camelcase'; 3 | import pick from 'lodash.pick'; 4 | import { useMemo } from 'react'; 5 | 6 | import { Listener } from '../listener'; 7 | 8 | type Props = { 9 | events: string[]; 10 | listeners: Record any>; 11 | }; 12 | 13 | export function HandleEvents(props: Props) { 14 | const { events, listeners: _listeners } = props; 15 | 16 | const eventMap = useMemo(() => createEventMap(events), events); 17 | const listeners = pick(_listeners, Object.keys(eventMap)) as unknown as Record void>; 18 | 19 | return ( 20 | <> 21 | {Object.keys(listeners).map(key => { 22 | 23 | const eventName = eventMap[key]; 24 | const listener = listeners[key]; 25 | 26 | return listener ? : null; 31 | })} 32 | 33 | ); 34 | } 35 | 36 | 37 | function createEventMap(events: string[]): Record { 38 | return events.reduce((acc, eventName) => { 39 | const key = camelcase(`on_${eventName}`); 40 | 41 | return { 42 | [key]: eventName, 43 | ...acc, 44 | }; 45 | }, {}); 46 | } 47 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/hooks/use-previous.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, DependencyList } from 'react'; 2 | 3 | export function usePrevious(state: T, deps: DependencyList): T | undefined { 4 | const ref = useRef(); 5 | 6 | useEffect(() => { 7 | ref.current = state; 8 | }, deps); 9 | 10 | return ref.current; 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export { NavermapsProvider } from './provider'; 4 | export { NaverMap } from './naver-map'; 5 | export { Container } from './container'; 6 | export { Circle } from './overlays/circle'; 7 | export { Ellipse } from './overlays/ellipse'; 8 | export { GroundOverlay } from './overlays/ground-overlay'; 9 | export { InfoWindow } from './overlays/info-window'; 10 | export { Marker } from './overlays/marker'; 11 | export { Polygon } from './overlays/polygon'; 12 | export { Polyline } from './overlays/polyline'; 13 | export { Rectangle } from './overlays/rectangle'; 14 | export type { Props as NaverMapsProviderProps } from './provider'; 15 | export type { Props as NaverMapProps } from './naver-map'; 16 | export type { Props as ContainerProps } from './container'; 17 | export type { Props as CircleProps } from './overlays/circle'; 18 | export type { Props as EllipseProps } from './overlays/ellipse'; 19 | export type { Props as GroundOverlayProps } from './overlays/ground-overlay'; 20 | export type { Props as InfoWindowProps } from './overlays/info-window'; 21 | export type { Props as MarkerProps } from './overlays/marker'; 22 | export type { Props as PolygonProps } from './overlays/polygon'; 23 | export type { Props as PolylineProps } from './overlays/polyline'; 24 | export type { Props as RectangleProps } from './overlays/rectangle'; 25 | 26 | export { LoadNavermapsScript, loadNavermapsScript } from './load-navermaps-script'; 27 | export { useNavermaps } from './use-navermaps'; 28 | 29 | export { NaverMapContext, useMap } from './contexts/naver-map'; 30 | export { ContainerContext, useContainerContext } from './contexts/container'; 31 | export type { ContainerContextType } from './contexts/container'; 32 | export { EventTargetContext, useEventTarget } from './contexts/event-target'; 33 | export { useListener, Listener } from './listener'; 34 | export type { Props as ListenerProps } from './listener'; 35 | export { Overlay } from './overlay'; 36 | export type { Props as OverlayProps } from './overlay'; 37 | 38 | export { NcpOptions, GovOptions, finOptions, ClientOptions } from './types/client'; 39 | export { UIEventHandlers } from './types/event'; 40 | 41 | /** 42 | * v0.0 alert을 위한 deprecated component 43 | */ 44 | export { RenderAfterNavermapsLoaded } from './deprecated'; 45 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/listener.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import type { FunctionComponent } from 'react'; 3 | 4 | import { useEventTarget } from './contexts/event-target'; 5 | import type { AllowedKey } from './types/utils'; 6 | 7 | export function useListener(target: any, type: string, listener: (...args: any[]) => void) { 8 | useEffect(() => { 9 | const _listener = (...args: any[]) => listener(...args, target); 10 | const mapEventListener = naver.maps.Event.addListener(target, type, _listener); 11 | 12 | return () => { 13 | naver.maps.Event.removeListener(mapEventListener); 14 | }; 15 | }, [target, type, listener]); 16 | } 17 | 18 | export interface Props { 19 | target?: any; 20 | type: string; 21 | listener: (...args: any[]) => any; 22 | } 23 | 24 | export const Listener: FunctionComponent = (props) => { 25 | const { 26 | target: propTarget, 27 | type, 28 | listener, 29 | } = props; 30 | 31 | const contextTarget = useEventTarget(); 32 | const target = propTarget || contextTarget; 33 | if (!target) { 34 | throw new Error('react-naver-maps: No Target to add listener'); 35 | } 36 | 37 | // TODO: FIX DefinitelyTyped 38 | useListener((target as unknown) as EventTarget, type, listener); 39 | 40 | return null; 41 | }; 42 | 43 | export function getListenerKeys

>(props: P) { 44 | return Object.keys(props).filter(key => /on[A-Z]\w+/.test(key)) as unknown as Array>; 45 | } 46 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/load-navermaps-script.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import type { ReactElement } from 'react'; 3 | 4 | import type { ClientOptions } from './types/client'; 5 | import { loadScript } from './utils/load-script'; 6 | 7 | export function loadNavermapsScript(options: ClientOptions) { 8 | const url = makeUrl(options); 9 | 10 | // TODO: Caching Promise 11 | 12 | const promise = loadScript(url).then(() => { 13 | const navermaps = window.naver.maps; 14 | 15 | if (navermaps.jsContentLoaded) { 16 | return navermaps; 17 | } 18 | 19 | return new Promise(resolve => { 20 | navermaps.onJSContentLoaded = () => { 21 | resolve(navermaps); 22 | }; 23 | }); 24 | }); 25 | 26 | return promise; 27 | } 28 | 29 | function makeUrl(options: ClientOptions) { 30 | const submodules = options.submodules; 31 | 32 | const clientIdQuery = 'ncpKeyId' in options ? `ncpKeyId=${options.ncpKeyId}` : 33 | 'ncpClientId' in options 34 | ? `ncpClientId=${options.ncpClientId}` 35 | : 'govClientId' in options 36 | ? `govClientId=${options.govClientId}` 37 | : 'finClientId' in options 38 | ? `finClientId=${options.finClientId}` 39 | : undefined; 40 | 41 | if (!clientIdQuery) { 42 | throw new Error('react-naver-maps: ncpKeyId, ncpClientId, govClientId or finClientId is required'); 43 | } 44 | 45 | let url = `https://oapi.map.naver.com/openapi/v3/maps.js?${clientIdQuery}`; 46 | 47 | if (submodules) { 48 | url += `&submodules=${submodules.join(',')}`; 49 | } 50 | 51 | return url; 52 | } 53 | 54 | 55 | type Props = ClientOptions & { 56 | children: () => ReactElement; 57 | }; 58 | 59 | export function LoadNavermapsScript({ 60 | children: Children, 61 | ...options 62 | }: Props) { 63 | const [navermaps, setNavermaps] = useState(); 64 | 65 | useEffect(() => { 66 | loadNavermapsScript(options).then((maps) => { 67 | setNavermaps(maps); 68 | }); 69 | }, []); 70 | 71 | return ( 72 | (navermaps && Children) ? : null 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/naver-map.tsx: -------------------------------------------------------------------------------- 1 | import pick from 'lodash.pick'; 2 | import upperfirst from 'lodash.upperfirst'; 3 | import { forwardRef, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react'; 4 | import type { ReactNode } from 'react'; 5 | 6 | import { useContainerContext } from './contexts/container'; 7 | import { EventTargetContext } from './contexts/event-target'; 8 | import { NaverMapContext } from './contexts/naver-map'; 9 | import { HandleEvents } from './helpers/event'; 10 | import { usePrevious } from './hooks/use-previous'; 11 | import { useNavermaps } from './use-navermaps'; 12 | 13 | type MapPaddingOptions = { 14 | top?: number; 15 | right?: number; 16 | bottom?: number; 17 | left?: number; 18 | }; 19 | 20 | type MapOptions = { 21 | background?: string; 22 | baseTileOpacity?: number; 23 | /** 24 | * @type naver.maps.Bounds | naver.maps.BoundsLiteral | null 25 | */ 26 | bounds?: naver.maps.Bounds | naver.maps.BoundsLiteral | null; 27 | /** 28 | * @type naver.maps.Coord | naver.maps.CoordLiteral 29 | */ 30 | center?: naver.maps.Coord | naver.maps.CoordLiteral; 31 | disableDoubleClickZoom?: boolean; 32 | disableDoubleTapZoom?: boolean; 33 | disableKineticPan?: boolean; 34 | disableTwoFingerTapZoom?: boolean; 35 | draggable?: boolean; 36 | keyboardShortcuts?: boolean; 37 | logoControl?: boolean; 38 | logoControlOptions?: naver.maps.LogoControlOptions; 39 | mapDataControl?: boolean; 40 | mapDataControlOptions?: naver.maps.MapDataControlOptions; 41 | mapTypeControl?: boolean; 42 | mapTypeControlOptions?: naver.maps.MapTypeControlOptions; 43 | mapTypeId?: string; 44 | mapTypes?: naver.maps.MapTypeRegistry; 45 | maxBounds?: naver.maps.Bounds | naver.maps.BoundsLiteral | null; 46 | maxZoom?: number; 47 | minZoom?: number; 48 | padding?: MapPaddingOptions; 49 | pinchZoom?: boolean; 50 | resizeOrigin?: naver.maps.Position; 51 | scaleControl?: boolean; 52 | scaleControlOptions?: naver.maps.ScaleControlOptions; 53 | scrollWheel?: boolean; 54 | size?: naver.maps.Size | naver.maps.SizeLiteral; 55 | overlayZoomEffect?: string | null; 56 | tileSpare?: number; 57 | tileTransition?: boolean; 58 | zoom?: number; 59 | zoomControl?: boolean; 60 | zoomControlOptions?: naver.maps.ZoomControlOptions; 61 | zoomOrigin?: naver.maps.Coord | naver.maps.CoordLiteral | null; 62 | blankTileImage?: string | null; 63 | 64 | // special. 65 | centerPoint?: naver.maps.Point | naver.maps.PointLiteral; 66 | }; 67 | 68 | type Uncontrolled = { 69 | /** 70 | * Uncontrolled prop of mapTypeId 71 | */ 72 | defaultMapTypeId?: MapOptions['mapTypeId']; 73 | /** 74 | * Uncontrolled prop of size 75 | * @type naver.maps.Coord | naver.maps.CoordLiteral 76 | */ 77 | defaultSize?: MapOptions['size']; 78 | /** 79 | * Uncontrolled prop of bounds 80 | * @type naver.maps.Bounds | naver.maps.BoundsLiteral | null 81 | */ 82 | defaultBounds?: MapOptions['bounds']; 83 | /** 84 | * Uncontrolled prop of center 85 | * @type naver.maps.Coord | naver.maps.CoordLiteral 86 | */ 87 | defaultCenter?: MapOptions['center']; 88 | /** 89 | * Uncontrolled prop of zoom 90 | */ 91 | defaultZoom?: MapOptions['zoom']; 92 | /** 93 | * Uncontrolled prop of centerPoint 94 | * @type naver.maps.Point | naver.maps.PointLiteral 95 | */ 96 | defaultCenterPoint?: MapOptions['centerPoint']; 97 | }; 98 | 99 | type MapEventCallbacks = { 100 | onMapTypeIdChanged?: (value: string) => void; 101 | onMapTypeChanged?: (value: naver.maps.MapType) => void; 102 | onSizeChanged?: (value: naver.maps.Size) => void; 103 | onBoundsChanged?: (value: naver.maps.Bounds) => void; 104 | onCenterChanged?: (value: naver.maps.Coord) => void; 105 | onCenterPointChanged?: (value: naver.maps.Point) => void; 106 | onZoomChanged?: (value: number) => void; 107 | }; 108 | 109 | const basicMapOptionKeys: Array = [ 110 | 'background', 111 | 'baseTileOpacity', 112 | // 'bounds', 113 | // 'center', 114 | 'disableDoubleClickZoom', 115 | 'disableDoubleTapZoom', 116 | 'disableKineticPan', 117 | 'disableTwoFingerTapZoom', 118 | 'draggable', 119 | 'keyboardShortcuts', 120 | 'logoControl', 121 | 'logoControlOptions', 122 | 'mapDataControl', 123 | 'mapDataControlOptions', 124 | 'mapTypeControl', 125 | 'mapTypeControlOptions', 126 | // 'mapTypeId', 127 | 'mapTypes', 128 | 'maxBounds', 129 | 'maxZoom', 130 | 'minZoom', 131 | 'padding', 132 | 'pinchZoom', 133 | 'resizeOrigin', 134 | 'scaleControl', 135 | 'scaleControlOptions', 136 | 'scrollWheel', 137 | // 'size', 138 | 'overlayZoomEffect', 139 | 'tileSpare', 140 | 'tileTransition', 141 | // 'zoom', 142 | 'zoomControl', 143 | 'zoomControlOptions', 144 | 'zoomOrigin', 145 | 'blankTileImage', 146 | ]; 147 | 148 | const kvoKeys = [ 149 | 'mapTypeId', 150 | 'size', 151 | 'bounds', 152 | 'center', 153 | 'zoom', 154 | 'centerPoint', 155 | ] as const; 156 | 157 | const kvoEvents = [ 158 | ...kvoKeys.map(key => `${key}_changed`), 159 | 'mapType_changed', // special. https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Map.html#event:mapType_changed__anchor 160 | ]; 161 | const uiEvents = [ 162 | 'mousedown', 163 | 'mouseup', 164 | 'click', 165 | 'dblclick', 166 | 'rightclick', 167 | 'mouseover', 168 | 'mouseout', 169 | 'mousemove', 170 | 'dragstart', 171 | 'drag', 172 | 'dragend', 173 | 'touchstart', 174 | 'touchmove', 175 | 'touchend', 176 | 'pinchstart', 177 | 'pinch', 178 | 'pinchend', 179 | 'tap', 180 | 'longtap', 181 | 'twofingertap', 182 | 'doubletap', 183 | ] as const; 184 | const mapOnlyEvents = [ 185 | 'addLayer', 186 | 'idle', 187 | 'init', 188 | 'keydown', 189 | 'keyup', 190 | 'panning', 191 | 'projection_changed', 192 | 'removeLayer', 193 | 'resize', 194 | 'tilesloaded', 195 | 'zooming', 196 | ] as const; 197 | const events = [...uiEvents, ...kvoEvents, ...mapOnlyEvents]; 198 | 199 | // type FunctionTypeChildren = (nmap: naver.maps.Map) => React.ReactNode; 200 | 201 | const defaultOptionKeyMap = { 202 | mapTypeId: 'defaultMapTypeId', 203 | size: 'defaultSize', 204 | bounds: 'defaultBounds', 205 | center: 'defaultCenter', 206 | zoom: 'defaultZoom', 207 | centerPoint: 'defaultCenterPoint', 208 | } as const; 209 | 210 | export type Props = Uncontrolled & { 211 | /** 212 | * Map 관련 components 213 | */ 214 | children?: ReactNode; 215 | } & MapOptions & MapEventCallbacks; 216 | 217 | export const NaverMap = forwardRef(function NaverMap(props, ref) { 218 | const navermaps = useNavermaps(); 219 | const { element: mapDiv } = useContainerContext(); 220 | const [nmap, setNmap] = useState(); 221 | const nmapRef = useRef(); 222 | 223 | // https://github.com/facebook/react/issues/20090 224 | useLayoutEffect(() => { 225 | if (!mapDiv) { 226 | throw new Error('react-naver-maps: MapDiv is not found. Did you correctly wrap with `MapDiv`?'); 227 | } 228 | 229 | const basicMapOptions = pick(props, basicMapOptionKeys); 230 | const kvos = kvoKeys.reduce((acc, key) => { 231 | // default kvo 232 | if (props[defaultOptionKeyMap[key]]) { 233 | return { 234 | ...acc, 235 | [key]: props[defaultOptionKeyMap[key]], 236 | }; 237 | } 238 | 239 | // kvo 240 | if (props[key]) { 241 | return { 242 | ...acc, 243 | [key]: props[key], 244 | }; 245 | } 246 | 247 | return acc; 248 | }, {}); 249 | 250 | const _nmap = new navermaps.Map(mapDiv, { ...basicMapOptions, ...kvos }); 251 | setNmap(_nmap); 252 | // for ref hack 253 | nmapRef.current = _nmap; 254 | 255 | return () => { 256 | _nmap.destroy(); 257 | }; 258 | }, []); 259 | 260 | const uncontrolledOmittedProps = (Object.keys(props) as Array).reduce((acc, key) => { 261 | // kvo key가 defaultKvo key와 함께 있을 경우 무시한다. 262 | if (key in defaultOptionKeyMap && props[defaultOptionKeyMap[key as keyof typeof defaultOptionKeyMap]]) { 263 | return acc; 264 | } 265 | 266 | return { 267 | ...acc, 268 | [key]: props[key], 269 | }; 270 | }, {}) as Props; 271 | 272 | // nmap 이 layoutEffect에서 생성되므로 항상 Map이 존재한다. 273 | useImperativeHandle(ref, () => nmapRef.current); 274 | 275 | return ( 276 | <>{nmap && } 277 | ); 278 | }); 279 | 280 | function NaverMapCore({ nmap, children, ...mapProps }: Props & { nmap: naver.maps.Map }) { 281 | const basicMapOptions = pick(mapProps, basicMapOptionKeys); 282 | const { 283 | mapTypeId, 284 | size, 285 | bounds, 286 | center, 287 | centerPoint, 288 | zoom, 289 | } = mapProps; 290 | 291 | const prevKVOs = usePrevious({ 292 | mapTypeId, 293 | size, 294 | bounds, 295 | center, 296 | centerPoint, 297 | zoom, 298 | }, [ 299 | mapTypeId, 300 | size, 301 | bounds, 302 | center, 303 | centerPoint, 304 | zoom, 305 | ]); 306 | 307 | function getDirtyKVOs(keys: Array): Pick { 308 | return keys.reduce((acc, key) => { 309 | const currentValue = nmap[`get${upperfirst(key)}` as keyof naver.maps.Map](); 310 | const propValue = mapProps[key]; 311 | 312 | if (!propValue || prevKVOs && prevKVOs[key] === propValue) { 313 | return acc; 314 | } 315 | 316 | const isEqual = typeof currentValue.equals === 'function' ? currentValue.equals(propValue) : currentValue === propValue; 317 | 318 | if (isEqual) { 319 | return acc; 320 | } 321 | 322 | return { 323 | ...acc, 324 | [key]: propValue, 325 | }; 326 | }, {} as Pick); 327 | } 328 | 329 | useLayoutEffect(() => { 330 | nmap.setOptions(basicMapOptions); 331 | }, [Object.values(basicMapOptions)]); 332 | 333 | useLayoutEffect(() => { 334 | const updated = getDirtyKVOs(['size']).size; 335 | if (updated) { 336 | nmap.setSize(updated); 337 | } 338 | }, [size]); 339 | 340 | useLayoutEffect(() => { 341 | const updated = getDirtyKVOs(['mapTypeId']).mapTypeId; 342 | if (updated) { 343 | nmap.setMapTypeId(updated); 344 | } 345 | }, [mapTypeId]); 346 | 347 | useLayoutEffect(() => { 348 | const dirties = getDirtyKVOs(['bounds', 'center', 'centerPoint', 'zoom']); 349 | 350 | if (dirties.bounds) { 351 | // TODO 352 | nmap.fitBounds(dirties.bounds); 353 | 354 | // Ignore rest kvos 355 | return; 356 | } 357 | 358 | if (dirties.center && dirties.zoom) { 359 | 360 | nmap.morph(dirties.center, dirties.zoom); 361 | 362 | // Ignore rest kvos 363 | return; 364 | } 365 | 366 | if (dirties.centerPoint) { 367 | nmap.setCenterPoint(dirties.centerPoint); 368 | } 369 | 370 | if (dirties.center) { 371 | // TODO 372 | nmap.panTo(dirties.center, {}); 373 | } 374 | 375 | if (dirties.zoom) { 376 | nmap.setZoom(dirties.zoom); 377 | } 378 | }, [bounds, center, centerPoint, zoom]); 379 | 380 | return ( 381 | 382 | 383 | <> 384 | 388 | {children} 389 | 390 | 391 | 392 | ); 393 | } 394 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlay.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | 3 | import { NaverMapContext } from './contexts/naver-map'; 4 | import { Overlay } from './overlay'; 5 | 6 | describe('', () => { 7 | it('should currectly handle contexted map on render', () => { 8 | let m: any; 9 | const element = { 10 | setMap: jest.fn((map: any) => m = map), 11 | getMap: jest.fn(() => m), 12 | }; 13 | const map = {} as naver.maps.Map; 14 | 15 | const { unmount, rerender } = render( 16 | 17 | ); 18 | 19 | expect(element.setMap).not.toBeCalled(); 20 | rerender( 21 | 22 | ); 23 | 24 | expect(element.setMap).toHaveBeenLastCalledWith(map); 25 | 26 | unmount(); 27 | expect(element.setMap).toHaveBeenLastCalledWith(null); 28 | }); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlay.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import type { ReactNode } from 'react'; 3 | 4 | import { EventTargetContext } from './contexts/event-target'; 5 | import { useMap } from './contexts/naver-map'; 6 | 7 | type MapElementType = { 8 | setMap(map: naver.maps.Map | null): void; 9 | getMap(): naver.maps.Map | null; 10 | }; 11 | 12 | export type Props = { 13 | element: MapElementType; 14 | children?: ReactNode; 15 | autoMount?: boolean; 16 | }; 17 | 18 | export function Overlay(props: Props) { 19 | const { element, children, autoMount = true } = props; 20 | const nmap = useMap(); 21 | 22 | useEffect(() => { 23 | if (!autoMount) { 24 | return; 25 | } 26 | 27 | if (element.getMap() === nmap) { 28 | return; 29 | } 30 | 31 | element.setMap(nmap ? nmap : null); 32 | return () => { 33 | element.setMap(null); 34 | }; 35 | }, [nmap]); 36 | 37 | return ( 38 | 39 | {children} 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlays/circle.tsx: -------------------------------------------------------------------------------- 1 | import pick from 'lodash.pick'; 2 | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; 3 | 4 | import { HandleEvents } from '../helpers/event'; 5 | import { Overlay } from '../overlay'; 6 | import type { UIEventHandlers } from '../types/event'; 7 | import { useNavermaps } from '../use-navermaps'; 8 | import { omitUndefined } from '../utils/omit-undefined'; 9 | 10 | const primitiveKvoKeys = [ 11 | 'radius', 12 | 'strokeWeight', 13 | 'strokeOpacity', 14 | 'strokeColor', 15 | 'strokeStyle', 16 | 'strokeLineCap', 17 | 'strokeLineJoin', 18 | 'fillColor', 19 | 'fillOpacity', 20 | 'clickable', 21 | 'visible', 22 | 'zIndex', 23 | ] as const; 24 | const kvoKeys = [ 25 | ...primitiveKvoKeys, 26 | 'center', 27 | ] as const; 28 | const kvoEvents = kvoKeys.map(key => `${key}_changed`); 29 | const uiEvents = [ 30 | 'mousedown', 31 | 'mouseup', 32 | 'click', 33 | 'dblclick', 34 | 'rightclick', 35 | 'mouseover', 36 | 'mouseout', 37 | 'mousemove', 38 | ] as const; 39 | const events = [...uiEvents, ...kvoEvents]; 40 | 41 | type CircleOptions = { 42 | /** 43 | * center 44 | * @type naver.maps.Coord | naver.maps.CoordLiteral 45 | */ 46 | center: naver.maps.Coord | naver.maps.CoordLiteral; 47 | radius?: number; 48 | strokeWeight?: number; 49 | strokeOpacity?: number; 50 | strokeColor?: string; 51 | strokeStyle?: naver.maps.strokeStyleType; 52 | strokeLineCap?: naver.maps.strokeLineCapType; 53 | strokeLineJoin?: naver.maps.strokeLineJoinType; 54 | fillColor?: string; 55 | fillOpacity?: number; 56 | clickable?: boolean; 57 | visible?: boolean; 58 | zIndex?: number; 59 | }; 60 | 61 | export type Props = CircleOptions & { 62 | onCenterChanged?: (value: naver.maps.Coord) => void; 63 | onRadiusChanged?: (value: number) => void; 64 | onStrokeWeightChanged?: (value: number) => void; 65 | onStrokeOpacityChanged?: (value: number) => void; 66 | onStrokeColorChanged?: (value: string) => void; 67 | onStrokeStyleChanged?: (value: naver.maps.strokeStyleType) => void; 68 | onStrokeLineCapChanged?: (value: naver.maps.strokeLineCapType) => void; 69 | onStrokeLineJoinChanged?: (value: naver.maps.strokeLineJoinType) => void; 70 | onFillColorChanged?: (value: string) => void; 71 | onFillOpacityChanged?: (value: number) => void; 72 | onClickableChanged?: (event: boolean) => void; 73 | onVisibleChanged?: (event: boolean) => void; 74 | onZIndexChanged?: (event: number) => void; 75 | } & UIEventHandlers; 76 | 77 | export const Circle = forwardRef(function Circle(props, ref) { 78 | const { center } = props; 79 | const navermaps = useNavermaps(); 80 | const [circle] = useState(() => new navermaps.Circle(omitUndefined(pick(props, [...kvoKeys])) as CircleOptions)); 81 | 82 | useImperativeHandle(ref, () => circle); 83 | 84 | useEffect(() => { 85 | if (center && !circle.getCenter().equals(center as naver.maps.Point)) { 86 | circle.setCenter(center); 87 | } 88 | }, [center]); 89 | 90 | useEffect(() => { 91 | circle.setOptions(omitUndefined(pick(props, primitiveKvoKeys)) as CircleOptions); 92 | }, primitiveKvoKeys.map(key => props[key])); 93 | 94 | return ( 95 | 96 | 97 | 98 | ); 99 | }); 100 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlays/ellipse.tsx: -------------------------------------------------------------------------------- 1 | import pick from 'lodash.pick'; 2 | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; 3 | 4 | import { HandleEvents } from '../helpers/event'; 5 | import { Overlay } from '../overlay'; 6 | import type { UIEventHandlers } from '../types/event'; 7 | import { useNavermaps } from '../use-navermaps'; 8 | import { omitUndefined } from '../utils/omit-undefined'; 9 | 10 | const primitiveKvoKeys = [ 11 | 'strokeWeight', 12 | 'strokeOpacity', 13 | 'strokeColor', 14 | 'strokeStyle', 15 | 'strokeLineCap', 16 | 'strokeLineJoin', 17 | 'fillColor', 18 | 'fillOpacity', 19 | 'clickable', 20 | 'visible', 21 | 'zIndex', 22 | ] as const; 23 | const kvoKeys = [ 24 | ...primitiveKvoKeys, 25 | 'bounds', 26 | ] as const; 27 | const kvoEvents = kvoKeys.map(key => `${key}_changed`); 28 | const uiEvents = [ 29 | 'mousedown', 30 | 'mouseup', 31 | 'click', 32 | 'dblclick', 33 | 'rightclick', 34 | 'mouseover', 35 | 'mouseout', 36 | 'mousemove', 37 | ] as const; 38 | const events = [...uiEvents, ...kvoEvents]; 39 | 40 | type EllipseOptions = { 41 | /** 42 | * bounds 43 | * @type naver.maps.Bounds | naver.maps.BoundsLiteral 44 | */ 45 | bounds: naver.maps.Bounds | naver.maps.BoundsLiteral; 46 | strokeWeight?: number; 47 | strokeOpacity?: number; 48 | strokeColor?: string; 49 | strokeStyle?: naver.maps.strokeStyleType; 50 | strokeLineCap?: naver.maps.strokeLineCapType; 51 | strokeLineJoin?: naver.maps.strokeLineJoinType; 52 | fillColor?: string; 53 | fillOpacity?: number; 54 | clickable?: boolean; 55 | visible?: boolean; 56 | zIndex?: number; 57 | }; 58 | 59 | export type Props = EllipseOptions & { 60 | onBoundsChanged?: (value: naver.maps.Bounds) => void; 61 | onStrokeWeightChanged?: (value: number) => void; 62 | onStrokeOpacityChanged?: (value: number) => void; 63 | onStrokeColorChanged?: (value: string) => void; 64 | onStrokeStyleChanged?: (value: naver.maps.strokeStyleType) => void; 65 | onStrokeLineCapChanged?: (value: naver.maps.strokeLineCapType) => void; 66 | onStrokeLineJoinChanged?: (value: naver.maps.strokeLineJoinType) => void; 67 | onFillColorChanged?: (value: string) => void; 68 | onFillOpacityChanged?: (value: number) => void; 69 | onClickableChanged?: (event: boolean) => void; 70 | onVisibleChanged?: (event: boolean) => void; 71 | onZIndexChanged?: (event: number) => void; 72 | } & UIEventHandlers; 73 | 74 | export const Ellipse = forwardRef(function Ellipse(props, ref) { 75 | const { bounds } = props; 76 | const navermaps = useNavermaps(); 77 | const [ellipse] = useState(() => new navermaps.Ellipse(omitUndefined(pick(props, [...kvoKeys])) as EllipseOptions)); 78 | 79 | useImperativeHandle(ref, () => ellipse); 80 | 81 | useEffect(() => { 82 | ellipse.setOptions(omitUndefined(pick(props, primitiveKvoKeys)) as EllipseOptions); // TODO: FIX DefinilyTyped. setOptions의 assign type 은 Partial 이어야 함 83 | }, primitiveKvoKeys.map(key => props[key])); 84 | 85 | useEffect(() => { 86 | if (bounds && ellipse.getBounds().equals(bounds as naver.maps.Bounds)) { 87 | ellipse.setBounds(bounds); 88 | } 89 | }, [bounds]); 90 | 91 | return ( 92 | 93 | 94 | 95 | ); 96 | }); 97 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlays/ground-overlay.tsx: -------------------------------------------------------------------------------- 1 | import pick from 'lodash.pick'; 2 | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; 3 | 4 | import { HandleEvents } from '../helpers/event'; 5 | import { Overlay } from '../overlay'; 6 | import type { UIEventHandlers } from '../types/event'; 7 | import { useNavermaps } from '../use-navermaps'; 8 | 9 | const kvoKeys = [ 10 | 'clickable', 11 | 'opacity', 12 | ] as const; 13 | const kvoEvents = kvoKeys.map(key => `${key}_changed`); 14 | const uiEvents = [ 15 | 'mousedown', 16 | 'mouseup', 17 | 'click', 18 | 'dblclick', 19 | 'rightclick', 20 | 'mouseover', 21 | 'mouseout', 22 | 'mousemove', 23 | ] as const; 24 | const events = [...uiEvents, ...kvoEvents]; 25 | 26 | type GroundOverlayOptions = { 27 | clickable?: boolean; 28 | opacity?: number; 29 | }; 30 | 31 | export type Props = GroundOverlayOptions & { 32 | url: string; 33 | /** 34 | * bounds 35 | * @type naver.maps.Bounds | naver.maps.BoundsLiteral 36 | */ 37 | bounds: naver.maps.Bounds | naver.maps.BoundsLiteral; 38 | onOpacityChanged?: (value: number) => void; 39 | onClickableChanged?: (event: boolean) => void; 40 | } & UIEventHandlers; 41 | 42 | export const GroundOverlay = forwardRef(function GroundOverlay(props, ref) { 43 | const options = pick(props, kvoKeys); 44 | const { url, bounds } = props; 45 | const navermaps = useNavermaps(); 46 | const [groundOverlay, setGroundOverlay] = useState(() => new navermaps.GroundOverlay(url, bounds, options)); 47 | 48 | useImperativeHandle(ref, () => groundOverlay, [groundOverlay]); 49 | 50 | useEffect(() => { 51 | if (groundOverlay.getUrl() !== url || groundOverlay.getBounds().equals(bounds as naver.maps.Bounds)) { 52 | setGroundOverlay(new naver.maps.GroundOverlay(url, bounds, options)); 53 | } 54 | }, [url, bounds]); 55 | 56 | useEffect(() => { 57 | kvoKeys.forEach(key => { 58 | if (options[key] && groundOverlay.get(key) !== options[key]) { 59 | groundOverlay.set(key, options[key]); 60 | } 61 | }); 62 | }, kvoKeys.map(key => options[key])); 63 | 64 | return ( 65 | 66 | 67 | 68 | ); 69 | }); 70 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlays/info-window.tsx: -------------------------------------------------------------------------------- 1 | import pick from 'lodash.pick'; 2 | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; 3 | 4 | import { HandleEvents } from '../helpers/event'; 5 | import { Overlay } from '../overlay'; 6 | import type { UIEventHandlers } from '../types/event'; 7 | import { useNavermaps } from '../use-navermaps'; 8 | import { omitUndefined } from '../utils/omit-undefined'; 9 | 10 | const primitiveKvoKeys = [ 11 | 'content', 12 | 'zIndex', 13 | 'maxWidth', 14 | 'pixelOffset', 15 | 'backgroundColor', 16 | 'borderColor', 17 | 'borderWidth', 18 | 'disableAutoPan', 19 | 'disableAnchor', 20 | 'anchorSkew', 21 | 'anchorSize', 22 | 'anchorColor', 23 | ] as const; 24 | const kvoKeys = [ 25 | ...primitiveKvoKeys, 26 | 'position', 27 | ] as const; 28 | const kvoEvents = kvoKeys.map(key => `${key}_changed`); 29 | const uiEvents = [ 30 | 'mousedown', 31 | 'mouseup', 32 | 'click', 33 | 'dblclick', 34 | 'rightclick', 35 | 'mouseover', 36 | 'mouseout', 37 | 'mousemove', 38 | ] as const; 39 | const events = [...uiEvents, ...kvoEvents]; 40 | 41 | type InfoWindowOptions = { 42 | /** 43 | * position 44 | * @type naver.maps.Coord | naver.maps.CoordLiteral 45 | */ 46 | position?: naver.maps.Coord | naver.maps.CoordLiteral; 47 | content: string; 48 | zIndex?: number; 49 | maxWidth?: number; 50 | /** 51 | * @type naver.maps.Point | naver.maps.PointLiteral 52 | */ 53 | pixelOffset?: naver.maps.Point | naver.maps.PointLiteral; 54 | backgroundColor?: string; 55 | borderColor?: string; 56 | borderWidth?: number; 57 | disableAutoPan?: boolean; 58 | disableAnchor?: boolean; 59 | anchorSkew?: boolean; 60 | /** 61 | * @type naver.maps.Size | naver.maps.SizeLiteral 62 | */ 63 | anchorSize?: naver.maps.Size | naver.maps.SizeLiteral; 64 | anchorColor?: string; 65 | }; 66 | 67 | export type Props = InfoWindowOptions & { 68 | onPositionChanged?: (value: naver.maps.Coord) => void; 69 | onContentChanged?: (value: HTMLElement) => void; 70 | onZIndexChanged?: (value: number) => void; 71 | onMaxWidthChanged?: (value: number) => void; 72 | onPixelOffsetChanged?: (value: naver.maps.Point) => void; 73 | onBackgroundColorChanged?: (value: string) => void; 74 | onBorderColorChanged?: (value: string) => void; 75 | onBorderWidthChanged?: (value: number) => void; 76 | onDisableAutoPanChanged?: (value: boolean) => void; 77 | onDisableAnchorChanged?: (value: boolean) => void; 78 | onAnchorSkewChanged?: (value: boolean) => void; 79 | onAnchorSizeChanged?: (value: naver.maps.Size) => void; 80 | onAnchorColorChanged?: (value: string) => void; 81 | } & UIEventHandlers; 82 | 83 | export const InfoWindow = forwardRef(function InfoWindow(props, ref) { 84 | const { position } = props; 85 | const navermaps = useNavermaps(); 86 | const [infoWindow] = useState(() => new navermaps.InfoWindow(omitUndefined(pick(props, [...kvoKeys])) as InfoWindowOptions)); 87 | 88 | useImperativeHandle(ref, () => infoWindow); 89 | 90 | useEffect(() => { 91 | infoWindow.setOptions(omitUndefined(pick(props, primitiveKvoKeys)) as InfoWindowOptions); // TODO: FIX DefinilyTyped 92 | }, primitiveKvoKeys.map(key => props[key])); 93 | 94 | useEffect(() => { 95 | if (position && infoWindow.getPosition().equals(position as naver.maps.Point)) { 96 | infoWindow.setPosition(position); 97 | } 98 | }, [position]); 99 | 100 | return ( 101 | 102 | 103 | 104 | ); 105 | }); 106 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlays/marker.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render, waitFor } from '@testing-library/react'; 2 | import omit from 'lodash.omit'; 3 | import { ReactElement, Suspense } from 'react'; 4 | 5 | import { NaverMapContext } from '../contexts/naver-map'; 6 | import { Marker } from './marker'; 7 | 8 | const map = {} as naver.maps.Map; 9 | function renderOverlay(overlay: ReactElement) { 10 | return render(overlay, { 11 | wrapper: ({ children }) => ( 12 | 13 | {children} 14 | 15 | ), 16 | }); 17 | } 18 | const mockPosition = { equals: jest.fn(() => false) }; 19 | let options = {} as naver.maps.MarkerOptions; 20 | const mockMethods = { 21 | getMap: jest.fn(), 22 | setMap: jest.fn(), 23 | getOptions: jest.fn((key: keyof naver.maps.MarkerOptions) => Object.assign({ [key]: options[key] })), 24 | setOptions: jest.fn((opt: any) => { 25 | Object.assign(options, opt, { position: mockPosition }); 26 | }), 27 | getPosition: jest.fn(() => mockPosition), 28 | setPosition: jest.fn(), 29 | }; 30 | const mockMarker = jest.fn().mockImplementation((opt) => { 31 | Object.assign(options, opt, { position: mockPosition }); 32 | return mockMethods; 33 | }); 34 | 35 | describe('', () => { 36 | beforeEach(() => { 37 | options = {}; 38 | mockMarker.mockClear(); 39 | Object.values(mockMethods).forEach(mock => mock.mockClear()); 40 | // @ts-expect-error mocking navermaps client loader 41 | window.naver = { maps: { Marker: mockMarker } }; 42 | }); 43 | 44 | it('should currectly handle options without props', async () => { 45 | const { rerender, unmount } = renderOverlay(); 46 | await waitFor(() => expect(window.naver.maps).toBeTruthy()); 47 | 48 | expect(mockMethods.setMap).toBeCalledWith(map); 49 | expect(mockMethods.setPosition).not.toBeCalled(); 50 | expect(mockMethods.setOptions).not.toBeCalled(); 51 | 52 | const position = {} as naver.maps.Point; 53 | rerender(); 54 | expect(mockMethods.setPosition).toBeCalledWith(position); 55 | 56 | unmount(); 57 | expect(mockMethods.setMap).toBeCalledWith(null); 58 | }); 59 | 60 | it('should currectly handle options with props', () => { 61 | const position = {} as naver.maps.Point; 62 | const animation = 0; 63 | const icon = ''; 64 | const shape = {} as naver.maps.MarkerShape; 65 | const title = 'title'; 66 | const cursor = ''; 67 | const clickable = true; 68 | const draggable = true; 69 | const visible = true; 70 | const zIndex = 0; 71 | 72 | const { rerender } = renderOverlay(); 84 | 85 | expect(mockMethods.setMap).toBeCalledWith(map); 86 | expect(omit(options, ['position'])).toEqual({ 87 | animation, 88 | icon, 89 | shape, 90 | title, 91 | cursor, 92 | clickable, 93 | draggable, 94 | visible, 95 | zIndex, 96 | }); 97 | 98 | const diffPosition1 = {} as naver.maps.Coord; 99 | const diffTitle1 = 'title1'; 100 | const sameClickable1 = true; 101 | 102 | rerender(); 103 | expect(mockMethods.setPosition).toBeCalledWith(diffPosition1); 104 | expect(mockMethods.setOptions).toBeCalledWith({ title: diffTitle1 }); 105 | }); 106 | 107 | it('should ignore change when uncontrolled props is set', () => { 108 | const position = {} as naver.maps.Point; 109 | const title = 'title'; 110 | 111 | const { rerender } = renderOverlay(); 115 | 116 | const position1 = {} as naver.maps.Coord; 117 | const title1 = 'title1'; 118 | 119 | // position, defaultPosition 어느것이 변경되더라도 position이 변경되지 않아야한다. 120 | const prevCallCount = mockMethods.setPosition.mock.calls.length; 121 | rerender(); 122 | expect(prevCallCount).toBe(mockMethods.setPosition.mock.calls.length); 123 | expect(mockMethods.setOptions).toBeCalledWith({ title: title1 }); 124 | }); 125 | 126 | it('should ignore position change when position is equal', () => { 127 | const position = {} as naver.maps.Point; 128 | 129 | const { rerender } = renderOverlay(); 132 | 133 | const prevCallCount = mockMethods.setPosition.mock.calls.length; 134 | mockPosition.equals.mockImplementationOnce(() => true); 135 | rerender(); 136 | expect(prevCallCount).toBe(mockMethods.setPosition.mock.calls.length); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlays/marker.tsx: -------------------------------------------------------------------------------- 1 | import mapKeys from 'lodash.mapkeys'; 2 | import pick from 'lodash.pick'; 3 | import { forwardRef, useLayoutEffect, useImperativeHandle, useRef, useState } from 'react'; 4 | import { useFirstMountState } from 'react-use'; 5 | 6 | import { HandleEvents } from '../helpers/event'; 7 | import { Overlay } from '../overlay'; 8 | import type { UIEventHandlers } from '../types/event'; 9 | import { useNavermaps } from '../use-navermaps'; 10 | import { getKeys } from '../utils/get-keys'; 11 | import { omitUndefined } from '../utils/omit-undefined'; 12 | import { getUncontrolledKey, makeUncontrolledKeyMap, UncontrolledKey } from '../utils/uncontrolled'; 13 | 14 | const primitiveKeys = [ 15 | 'animation', 16 | 'icon', 17 | 'shape', 18 | 'title', 19 | 'cursor', 20 | 'clickable', 21 | 'draggable', 22 | 'visible', 23 | 'zIndex', 24 | ] as const; 25 | const locationalKeys = ['position'] as const; 26 | const uncontrolledKeyMap = makeUncontrolledKeyMap(locationalKeys); 27 | const kvoKeys = [ 28 | ...primitiveKeys, 29 | ...locationalKeys, 30 | ] as const; 31 | const kvoEvents = kvoKeys.map(key => `${key}_changed`); 32 | const uiEvents = [ 33 | 'mousedown', 34 | 'mouseup', 35 | 'click', 36 | 'dblclick', 37 | 'rightclick', 38 | 'mouseover', 39 | 'mouseout', 40 | 'dragstart', 41 | 'drag', 42 | 'dragend', 43 | ] as const; 44 | const events = [...uiEvents, ...kvoEvents]; 45 | 46 | type MarkerKVO = { 47 | /** Animation??? */ 48 | animation: naver.maps.Animation; 49 | position: naver.maps.Coord | naver.maps.CoordLiteral; 50 | icon: string | naver.maps.ImageIcon | naver.maps.SymbolIcon | naver.maps.HtmlIcon; 51 | shape: naver.maps.MarkerShape; 52 | title: string; 53 | cursor: string; 54 | clickable: boolean; 55 | draggable: boolean; 56 | visible: boolean; 57 | zIndex: number; 58 | }; 59 | 60 | type UncontrolledProps = { 61 | [key in typeof locationalKeys[number] as UncontrolledKey]: MarkerKVO[key]; 62 | }; 63 | 64 | // TODO: Fix DefinitelyTyped 65 | type MarkerOptions = Partial; 66 | 67 | export type Props = MarkerOptions & Partial & UIEventHandlers & { 68 | onAnimationChanged?: (value: naver.maps.Animation) => void; 69 | onPositionChanged?: (value: naver.maps.Coord) => void; 70 | onIconChanged?: (value: string | naver.maps.ImageIcon | naver.maps.HtmlIcon | naver.maps.SymbolIcon) => void; 71 | onShapeChanged?: (event: naver.maps.MarkerShape) => void; 72 | onTitleChanged?: (event: string) => void; 73 | onCursorChanged?: (event: string) => void; 74 | onClickableChanged?: (event: boolean) => void; 75 | onDraggableChanged?: (event: boolean) => void; 76 | onVisibleChanged?: (event: boolean) => void; 77 | /** 78 | * hello yeah 79 | * @param event helo? 80 | * @returns 81 | */ 82 | onZIndexChanged?: (event: number) => void; 83 | }; 84 | 85 | function makeInitialOption(props: Props) { 86 | const uncontrolledProps = pick(props, getKeys(uncontrolledKeyMap)); 87 | const prefixCleared = mapKeys(uncontrolledProps, (_, key) => uncontrolledKeyMap[key as keyof typeof uncontrolledKeyMap]); 88 | const kvoProps = pick(props, kvoKeys); 89 | 90 | return omitUndefined({ ...kvoProps, ...prefixCleared }); 91 | } 92 | 93 | function isLocationalKey(key: string): key is typeof locationalKeys[number] { 94 | return locationalKeys.includes(key as typeof locationalKeys[number]); 95 | } 96 | 97 | function isEqualKvo(kvo: any, target: any) { 98 | if (kvo === undefined) { 99 | return false; 100 | } 101 | 102 | if (kvo === target) { 103 | return true; 104 | } 105 | 106 | try { 107 | return kvo.equals(target); 108 | } catch { 109 | return kvo === target; 110 | } 111 | } 112 | 113 | export const Marker = forwardRef(function Marker(props, ref) { 114 | const navermaps = useNavermaps(); 115 | const [marker] = useState(() => new navermaps.Marker(makeInitialOption(props))); 116 | useImperativeHandle(ref, () => marker); 117 | 118 | // make dirties 119 | const isFirst = useFirstMountState(); 120 | const dirtiesRef = useRef>({}); 121 | dirtiesRef.current = getDirties(); 122 | 123 | function getDirties() { 124 | // initialize의 option과 중복되지 않도록 첫 렌더시 제외한다. 125 | if (isFirst) { 126 | return {}; 127 | } 128 | 129 | return kvoKeys.reduce((acc, key) => { 130 | if (props[key] === undefined) { 131 | return acc; 132 | } 133 | 134 | if (isLocationalKey(key) && props[getUncontrolledKey(key)] !== undefined) { 135 | return acc; 136 | } 137 | 138 | const kvos = marker.getOptions(key); 139 | if (isEqualKvo(kvos[key], props[key])) { 140 | return acc; 141 | } 142 | 143 | return { 144 | ...acc, 145 | [key]: props[key], 146 | }; 147 | }, {}); 148 | } 149 | 150 | function pickDirties(keys: readonly string[]) { 151 | return pick(dirtiesRef.current, keys); 152 | } 153 | 154 | // side effects 155 | useLayoutEffect(() => { 156 | const { position } = pickDirties(['position']); 157 | if (position) { 158 | marker.setPosition(position); 159 | } 160 | }, [dirtiesRef.current['position']]); 161 | 162 | useLayoutEffect(() => { 163 | const dirties = pickDirties(primitiveKeys); 164 | if (Object.values(dirties).length < 1) { 165 | return; 166 | } 167 | 168 | marker.setOptions(dirties); 169 | }, primitiveKeys.map(key => dirtiesRef.current[key])); 170 | 171 | return ( 172 | 173 | 174 | 175 | ); 176 | }); 177 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlays/polygon.tsx: -------------------------------------------------------------------------------- 1 | import pick from 'lodash.pick'; 2 | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; 3 | 4 | import { HandleEvents } from '../helpers/event'; 5 | import { Overlay } from '../overlay'; 6 | import type { UIEventHandlers } from '../types/event'; 7 | import { useNavermaps } from '../use-navermaps'; 8 | import { omitUndefined } from '../utils/omit-undefined'; 9 | 10 | const kvoKeys = [ 11 | 'paths', 12 | 'strokeWeight', 13 | 'strokeOpacity', 14 | 'strokeColor', 15 | 'strokeStyle', 16 | 'strokeLineCap', 17 | 'strokeLineJoin', 18 | 'fillColor', 19 | 'fillOpacity', 20 | 'clickable', 21 | 'visible', 22 | 'zIndex', 23 | ] as const; 24 | const kvoEvents = kvoKeys.map(key => `${key}_changed`); 25 | const uiEvents = [ 26 | 'mousedown', 27 | 'mouseup', 28 | 'click', 29 | 'dblclick', 30 | 'rightclick', 31 | 'mouseover', 32 | 'mouseout', 33 | 'mousemove', 34 | ] as const; 35 | const events = [...uiEvents, ...kvoEvents]; 36 | 37 | type PolygonOptions = { 38 | /** 39 | * @type naver.maps.ArrayOfCoords[] | naver.maps.KVOArrayOfCoords[] | naver.maps.ArrayOfCoordsLiteral[] 40 | */ 41 | paths: naver.maps.ArrayOfCoords[] | naver.maps.KVOArrayOfCoords[] | naver.maps.ArrayOfCoordsLiteral[]; 42 | strokeWeight?: number; 43 | strokeOpacity?: number; 44 | strokeColor?: string; 45 | strokeStyle?: naver.maps.strokeStyleType; 46 | strokeLineCap?: naver.maps.strokeLineCapType; 47 | strokeLineJoin?: naver.maps.strokeLineJoinType; 48 | fillColor?: string; 49 | fillOpacity?: number; 50 | clickable?: boolean; 51 | visible?: boolean; 52 | zIndex?: number; 53 | }; 54 | 55 | export type Props = PolygonOptions & { 56 | onPathsChanged?: (value: Array) => void; 57 | onStrokeWeightChanged?: (value: number) => void; 58 | onStrokeOpacityChanged?: (value: number) => void; 59 | onStrokeColorChanged?: (value: string) => void; 60 | onStrokeStyleChanged?: (value: naver.maps.strokeStyleType) => void; 61 | onStrokeLineCapChanged?: (value: naver.maps.strokeLineCapType) => void; 62 | onStrokeLineJoinChanged?: (value: naver.maps.strokeLineJoinType) => void; 63 | onFillColorChanged?: (value: string) => void; 64 | onFillOpacityChanged?: (value: number) => void; 65 | onClickableChanged?: (event: boolean) => void; 66 | onVisibleChanged?: (event: boolean) => void; 67 | onZIndexChanged?: (event: number) => void; 68 | } & UIEventHandlers; 69 | 70 | export const Polygon = forwardRef(function Polygon(props, ref) { 71 | const options = pick(props, [...kvoKeys]); 72 | const navermaps = useNavermaps(); 73 | const [polygon] = useState(() => new navermaps.Polygon(options)); 74 | 75 | useImperativeHandle(ref, () => polygon); 76 | 77 | useEffect(() => { 78 | polygon.setOptions(omitUndefined(options) as PolygonOptions); // TODO: FIX DefinilyTyped. setOptions의 assign type 은 Partial 이어야 함 79 | }, kvoKeys.map(key => options[key])); 80 | 81 | return ( 82 | 83 | 84 | 85 | ); 86 | }); 87 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlays/polyline.tsx: -------------------------------------------------------------------------------- 1 | import pick from 'lodash.pick'; 2 | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; 3 | 4 | import { HandleEvents } from '../helpers/event'; 5 | import { Overlay } from '../overlay'; 6 | import type { UIEventHandlers } from '../types/event'; 7 | import { useNavermaps } from '../use-navermaps'; 8 | import { omitUndefined } from '../utils/omit-undefined'; 9 | 10 | const kvoKeys = [ 11 | 'path', 12 | 'strokeWeight', 13 | 'strokeOpacity', 14 | 'strokeColor', 15 | 'strokeStyle', 16 | 'strokeLineCap', 17 | 'strokeLineJoin', 18 | 'clickable', 19 | 'visible', 20 | 'zIndex', 21 | 'startIcon', 22 | 'startIconSize', 23 | 'endIcon', 24 | 'endIconSize', 25 | ] as const; 26 | const kvoEvents = kvoKeys.map(key => `${key}_changed`); 27 | const uiEvents = [ 28 | 'mousedown', 29 | 'mouseup', 30 | 'click', 31 | 'dblclick', 32 | 'rightclick', 33 | 'mouseover', 34 | 'mouseout', 35 | 'mousemove', 36 | ] as const; 37 | const events = [...uiEvents, ...kvoEvents]; 38 | 39 | type PolylineOptions = { 40 | /** 41 | * @type naver.maps.ArrayOfCoords | naver.maps.KVOArrayOfCoords | naver.maps.ArrayOfCoordsLiteral 42 | */ 43 | path: naver.maps.ArrayOfCoords | naver.maps.KVOArrayOfCoords | naver.maps.ArrayOfCoordsLiteral; 44 | strokeWeight?: number; 45 | strokeOpacity?: number; 46 | strokeColor?: string; 47 | strokeStyle?: naver.maps.strokeStyleType; 48 | strokeLineCap?: naver.maps.strokeLineCapType; 49 | strokeLineJoin?: naver.maps.strokeLineJoinType; 50 | clickable?: boolean; 51 | visible?: boolean; 52 | zIndex?: number; 53 | startIcon?: naver.maps.PointingIcon; 54 | startIconSize?: number; 55 | endIcon?: naver.maps.PointingIcon; 56 | endIconSize?: number; 57 | }; 58 | 59 | export type Props = PolylineOptions & { 60 | onPathChanged?: (value: naver.maps.ArrayOfCoords) => void; 61 | onStrokeWeightChanged?: (value: number) => void; 62 | onStrokeOpacityChanged?: (value: number) => void; 63 | onStrokeColorChanged?: (value: string) => void; 64 | onStrokeStyleChanged?: (value: naver.maps.strokeStyleType) => void; 65 | onStrokeLineCapChanged?: (value: naver.maps.strokeLineCapType) => void; 66 | onStrokeLineJoinChanged?: (value: naver.maps.strokeLineJoinType) => void; 67 | onClickableChanged?: (value: boolean) => void; 68 | onVisibleChanged?: (value: boolean) => void; 69 | onZIndexChanged?: (value: number) => void; 70 | onStartIconChanged?: (value: naver.maps.PointingIcon) => void; 71 | onStartIconSizeChanged?: (number: string) => void; 72 | onEndIconChanged?: (value: naver.maps.PointingIcon) => void; 73 | onEndIconSizeChanged?: (number: string) => void; 74 | } & UIEventHandlers; 75 | 76 | export const Polyline = forwardRef(function Polyline(props, ref) { 77 | const options = pick(props, [...kvoKeys]); 78 | const navermaps = useNavermaps(); 79 | const [polyline] = useState(() => new navermaps.Polyline(options)); 80 | 81 | useImperativeHandle(ref, () => polyline); 82 | 83 | useEffect(() => { 84 | polyline.setOptions(omitUndefined(options) as PolylineOptions); // TODO: FIX DefinilyTyped. setOptions의 assign type 은 Partial 이어야 함 85 | }, kvoKeys.map(key => options[key])); 86 | 87 | return ( 88 | 89 | 90 | 91 | ); 92 | }); 93 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/overlays/rectangle.tsx: -------------------------------------------------------------------------------- 1 | import pick from 'lodash.pick'; 2 | import { useEffect, useState, forwardRef, useImperativeHandle } from 'react'; 3 | 4 | import { HandleEvents } from '../helpers/event'; 5 | import { Overlay } from '../overlay'; 6 | import type { UIEventHandlers } from '../types/event'; 7 | import { useNavermaps } from '../use-navermaps'; 8 | import { omitUndefined } from '../utils/omit-undefined'; 9 | 10 | const optionKeys = [ 11 | 'strokeWeight', 12 | 'strokeOpacity', 13 | 'strokeColor', 14 | 'strokeStyle', 15 | 'strokeLineCap', 16 | 'strokeLineJoin', 17 | 'fillColor', 18 | 'fillOpacity', 19 | ] as const; 20 | const kvoKeys = [ 21 | 'bounds', 22 | 'clickable', 23 | 'visible', 24 | 'zIndex', 25 | ] as const; 26 | const kvoEvents = kvoKeys.map(key => `${key}_changed`); 27 | const uiEvents = [ 28 | 'click', 29 | 'dblclick', 30 | 'mousedown', 31 | 'mouseout', 32 | 'mouseover', 33 | 'mouseup', 34 | ] as const; 35 | const events = [...uiEvents, ...kvoEvents]; 36 | 37 | type RectangleOptions = { 38 | /** 39 | * @type naver.maps.Bounds | naver.maps.BoundsLiteral 40 | */ 41 | bounds: naver.maps.Bounds | naver.maps.BoundsLiteral; 42 | strokeWeight?: number; 43 | strokeOpacity?: number; 44 | strokeColor?: string; 45 | strokeStyle?: naver.maps.strokeStyleType; 46 | strokeLineCap?: naver.maps.strokeLineCapType; 47 | strokeLineJoin?: naver.maps.strokeLineJoinType; 48 | fillColor?: string; 49 | fillOpacity?: number; 50 | clickable?: boolean; 51 | visible?: boolean; 52 | zIndex?: number; 53 | }; 54 | 55 | export type Props = RectangleOptions & { 56 | onBoundsChanged?: (value: naver.maps.Bounds) => void; 57 | onClickableChanged?: (value: boolean) => void; 58 | onVisibleChanged?: (value: boolean) => void; 59 | onZIndexChanged?: (value: number) => void; 60 | } & UIEventHandlers; 61 | 62 | export const Rectangle = forwardRef(function Rectangle(props, ref) { 63 | const options = pick(props, [...optionKeys, ...kvoKeys]); 64 | const navermaps = useNavermaps(); 65 | const [rectangle] = useState(() => new navermaps.Rectangle(options)); 66 | 67 | useImperativeHandle(ref, () => rectangle); 68 | 69 | useEffect(() => { 70 | rectangle.setOptions(omitUndefined(options) as RectangleOptions); // TODO: FIX DefinilyTyped. setOptions의 assign type 은 Partial 이어야 함 71 | }, kvoKeys.map(key => options[key])); 72 | 73 | return ( 74 | 75 | 76 | 77 | ); 78 | }); 79 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/provider.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | 3 | import { ClientOptionsContext } from './contexts/client-options'; 4 | import type { ClientOptions } from './types/client'; 5 | 6 | export type Props = ClientOptions & { children?: ReactNode }; 7 | 8 | export function NavermapsProvider({ 9 | children, 10 | ...clientOptions 11 | }: Props) { 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/types/case.ts: -------------------------------------------------------------------------------- 1 | export type SnakeToCamelCase = 2 | S extends `${infer T}_${infer U}` ? 3 | `${T}${Capitalize>}` : 4 | S; 5 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/types/client.ts: -------------------------------------------------------------------------------- 1 | export type CommonOptions = { 2 | submodules?: string[]; 3 | /** 4 | * ncpKeyId로 대체됨 5 | * */ 6 | ncpKeyId: string; 7 | }; 8 | 9 | /** @deprecated */ 10 | export type NcpOptions = { 11 | submodules?: string[]; 12 | /** 13 | * @deprecated ncpKeyId로 대체 14 | * ncpClientId, govClientId, finClientId 중 선택 15 | */ 16 | ncpClientId: string; 17 | }; 18 | 19 | /** @deprecated */ 20 | export type GovOptions = { 21 | submodules?: string[]; 22 | /** 23 | * @deprecated ncpKeyId로 대체 24 | * ncpClientId, govClientId, finClientId 중 선택 25 | */ 26 | govClientId: string; 27 | }; 28 | 29 | /** @deprecated */ 30 | export type finOptions = { 31 | submodules?: string[]; 32 | /** 33 | * @deprecated ncpKeyId로 대체 34 | * ncpClientId, govClientId, finClientId 중 선택 35 | */ 36 | finClientId: string; 37 | }; 38 | 39 | export type ClientOptions = CommonOptions | NcpOptions | GovOptions | finOptions; 40 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/types/event.ts: -------------------------------------------------------------------------------- 1 | export type UIEventHandlers = Partial}`, (e: naver.maps.PointerEvent) => void>>; 2 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/types/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | type FilterFlags = { 3 | [Key in keyof Base]: Key extends Condition ? Key : never 4 | }; 5 | 6 | export type AllowedKey = FilterFlags[keyof Base]; 7 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/use-navermaps.spec.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook, fireEvent, waitFor } from '@testing-library/react'; 2 | import { Suspense } from 'react'; 3 | 4 | import { ClientOptionsContext } from './contexts/client-options'; 5 | import { useNavermaps } from './use-navermaps'; 6 | 7 | const testId = 'test-naver-cilent-id'; 8 | const naverMock = { maps: { jsContentLoaded: true } }; 9 | 10 | describe('useNavermaps()', () => { 11 | test('Suspense client fetching', async () => { 12 | const wrapper = ({ children }: { children: any }) => ( 13 | 14 | 15 | {children} 16 | 17 | 18 | ); 19 | 20 | const { result } = renderHook(() => useNavermaps(), { wrapper }); 21 | expect(document.head.innerHTML).toMatch(new RegExp(`^. Instead of mock & fetch navermaps cdn client, 24 | // we just fire onload event to await loadNavermapsScript 25 | // @ts-expect-error mocking navermaps client loader 26 | window.naver = naverMock; 27 | fireEvent( 28 | document.getElementsByTagName('script')[0], 29 | new Event('load'), 30 | ); 31 | 32 | await waitFor(() => expect(result.current).not.toBeNull()); 33 | 34 | expect(result.current).toBe(naverMock.maps); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/use-navermaps.ts: -------------------------------------------------------------------------------- 1 | import { suspend } from 'suspend-react'; 2 | 3 | import { useClientOptions } from './contexts/client-options'; 4 | import { loadNavermapsScript } from './load-navermaps-script'; 5 | import type { ClientOptions } from './types/client'; 6 | 7 | async function load(options?: ClientOptions): Promise { 8 | if (typeof window !== 'undefined' && window.naver?.maps) { 9 | return window.naver.maps; 10 | } 11 | 12 | if (!options) { 13 | throw new Error('react-naver-maps: set options with `useNavermaps.config`'); 14 | } 15 | 16 | return await loadNavermapsScript(options); 17 | } 18 | 19 | export function useNavermaps() { 20 | if (typeof window === 'undefined') { 21 | throw new Error('react-naver-maps: browser'); 22 | } 23 | 24 | /** 25 | * TODO: Provider option 이 변경될 경우 클리어하는 로직 필요 26 | * ex) submodule 에 파노라마 추가시 window.naver.maps가 존재하므로 새로 로드하지 않음 27 | */ 28 | if (window.naver?.maps) { 29 | return window.naver.maps; 30 | } 31 | 32 | const options = useClientOptions(); 33 | 34 | return suspend(load, [options, 'react-naver-maps/loadClient']); 35 | } 36 | 37 | // useNavermaps.preload = (options: any) => { 38 | // if (!window) { 39 | // return; 40 | // } 41 | 42 | // return preload(load, [options, 'react-naver-maps/loadClient']); 43 | // }; 44 | 45 | // useNavermaps.clear = (options: any) => { 46 | // return clear([options, 'react-naver-maps/loadClient']); 47 | // }; 48 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/utils/get-keys.ts: -------------------------------------------------------------------------------- 1 | export function getKeys>(obj: T): Array { 2 | return Object.keys(obj); 3 | } 4 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/utils/load-script.ts: -------------------------------------------------------------------------------- 1 | import _loadScript from 'load-script'; 2 | 3 | export function loadScript(src: string): Promise { 4 | return new Promise((resolve, reject) => { 5 | _loadScript(src, (err, script) => { 6 | if (err) reject(err); 7 | else resolve(script); 8 | }); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/utils/omit-undefined.ts: -------------------------------------------------------------------------------- 1 | export function omitUndefined>(obj: T): Partial { 2 | return Object.keys(obj).reduce((acc, key) => { 3 | if (obj[key] === 'undefined') { 4 | return acc; 5 | } 6 | return { 7 | ...acc, 8 | [key]: obj[key], 9 | }; 10 | }, {}); 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-naver-maps/src/utils/uncontrolled.ts: -------------------------------------------------------------------------------- 1 | import camelcase from 'camelcase'; 2 | 3 | export type UncontrolledKey = `default${Capitalize}`; 4 | 5 | export function getUncontrolledKey(key: T): UncontrolledKey { 6 | return camelcase(`default_${key}`) as UncontrolledKey; 7 | } 8 | 9 | export function makeUncontrolledKeyMap(keys: T) { 10 | return keys.reduce((acc, key) => ({ ...acc, [getUncontrolledKey(key)]: key }), {}) as { 11 | [key in typeof keys[number] as UncontrolledKey]: key; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /packages/react-naver-maps/test/naver-map.test.tsx: -------------------------------------------------------------------------------- 1 | // import { render, fireEvent, waitFor } from '@testing-library/react'; 2 | // import { renderHook, act } from '@testing-library/react-hooks'; 3 | // import { MapDiv } from '../map-div'; 4 | // import { NaverMap } from '../naver-map'; 5 | 6 | // window.naver = {} as any; 7 | // window.naver.maps = {} as any; 8 | // window.naver.maps.Map = jest.fn().mockImplementation(() => { 9 | // return { 10 | // destroy: () => { console.log('naver.maps.Map destroyed'); }, 11 | // getSize: () => new ObjectValue(), 12 | // getCenter: () => new ObjectValue(), 13 | // getBounds: () => new ObjectValue(), 14 | // getZoom: () => 10, 15 | // getMapTypeId: () => 'test', 16 | // getCenterPoint: () => new ObjectValue(), 17 | // setSize: mockSetSize, 18 | // panToBounds: mockPanToBounds, 19 | // setCenter: mockSetCenter, 20 | // setMapTypeId: mockSetMapTypeId, 21 | // setOptions: mockSetOptions, 22 | // }; 23 | // }); 24 | 25 | // const ObjectValue = jest.fn().mockImplementation(() => { 26 | // return { equals: (obj: any) => false }; 27 | // }); 28 | // const mockSetSize = jest.fn(); 29 | // const mockPanToBounds = jest.fn(); 30 | // const mockSetCenter = jest.fn(); 31 | // const mockSetMapTypeId = jest.fn(); 32 | // const mockSetOptions = jest.fn(); 33 | 34 | // const mockMethods = [ 35 | // mockSetSize, 36 | // mockSetMapTypeId, 37 | // mockPanToBounds, 38 | // mockSetCenter, 39 | // ]; 40 | 41 | // beforeEach(() => { 42 | // (window.naver.maps.Map as any).mockClear(); 43 | // mockMethods.forEach(f => f.mockClear()); 44 | // }); 45 | 46 | // async function waitMapRendered() { 47 | // await waitFor(() => { 48 | // expect(window.naver.maps.Map).toHaveBeenCalledTimes(1); 49 | // }); 50 | // } 51 | 52 | // describe('Map', () => { 53 | // test('size', async () => { 54 | // const { rerender } = render( 55 | // 56 | // ); 57 | 58 | // await waitMapRendered(); 59 | 60 | // rerender( 61 | // 62 | // ); 63 | 64 | // expect(mockSetSize).toBeCalledTimes(1); 65 | // }); 66 | 67 | // test('mapTypeId', async () => { 68 | // const { rerender } = render( 69 | // 70 | // ); 71 | 72 | // await waitMapRendered(); 73 | 74 | // rerender( 75 | // 76 | // ); 77 | 78 | // rerender( 79 | // 80 | // ); 81 | 82 | // rerender( 83 | // 84 | // ); 85 | 86 | // expect(mockSetMapTypeId).toBeCalledTimes(2); 87 | // }); 88 | 89 | // test('bounds', async () => { 90 | // const { rerender } = render( 91 | // 92 | // ); 93 | 94 | // await waitMapRendered(); 95 | 96 | // rerender( 97 | // 101 | // ); 102 | 103 | // expect(mockPanToBounds).toBeCalledTimes(1); 104 | // expect(mockSetCenter).toBeCalledTimes(0); 105 | // }); 106 | // }); 107 | 108 | describe('useNavermaps()', () => { 109 | test('hello', () => { 110 | expect(1).toBe(1); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /packages/react-naver-maps/test/setupTests.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeakd/react-naver-maps/1de4aa19a58ef2a4044a3fd58448bba0134611e4/packages/react-naver-maps/test/setupTests.ts -------------------------------------------------------------------------------- /packages/react-naver-maps/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | "jsx": "react-jsx", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | "declarationDir": "./types", 15 | // "emitDeclarationOnly": true, 16 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 17 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 18 | // "outFile": "./", /* Concatenate and emit output to single file. */ 19 | // "outDir": "./dist", /* Redirect output structure to the directory. */ 20 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 21 | // "composite": true, /* Enable project compilation */ 22 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 23 | // "removeComments": true, /* Do not emit comments to output. */ 24 | // "noEmit": true, /* Do not emit outputs. */ 25 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 28 | 29 | /* Strict Type-Checking Options */ 30 | "strict": true, /* Enable all strict type-checking options. */ 31 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 32 | // "strictNullChecks": true, /* Enable strict null checks. */ 33 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 34 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 35 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 36 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 37 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 38 | 39 | /* Additional Checks */ 40 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 41 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 42 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 43 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 44 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 45 | 46 | /* Module Resolution Options */ 47 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | "types": ["navermaps", "jest"], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | // "skipLibCheck": true, /* Skip type checking of declaration files. */ 70 | // "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | }, 72 | "include": ["src", "test"], 73 | "exclude": ["node_modules"], 74 | } 75 | -------------------------------------------------------------------------------- /packages/react-naver-maps/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | import pkgJson from './package.json'; 3 | 4 | const external = [...Object.keys(pkgJson.dependencies || {}), ...Object.keys(pkgJson.peerDependencies || {})]; 5 | 6 | export default defineConfig({ 7 | entry: ['src/**/*.ts?(x)', '!src/**/*.(spec|test).ts?(x)'], 8 | format: ['cjs', 'esm'], 9 | external, 10 | dts: true, 11 | sourcemap: true, 12 | }); 13 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - website -------------------------------------------------------------------------------- /website/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['../.eslintrc.js'], 3 | rules: { 'react/react-in-jsx-scope': 'off' }, 4 | ignorePatterns: ['**/samples/*.js'], 5 | }; 6 | -------------------------------------------------------------------------------- /website/.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 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | -------------------------------------------------------------------------------- /website/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # website 2 | 3 | ## 0.1.0 4 | 5 | ### Minor Changes 6 | 7 | - 50c7d39: release v0.1.0 8 | 9 | ### Patch Changes 10 | 11 | - Updated dependencies [96c6bb0] 12 | - Updated dependencies [50c7d39] 13 | - Updated dependencies [894ccb1] 14 | - react-naver-maps@0.1.0 15 | 16 | ## 0.1.0-next.0 17 | 18 | ### Minor Changes 19 | 20 | - release v0.1.0 21 | 22 | ### Patch Changes 23 | 24 | - Updated dependencies 25 | - react-naver-maps@0.1.0-next.0 26 | -------------------------------------------------------------------------------- /website/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 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 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 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /website/additional.d.ts: -------------------------------------------------------------------------------- 1 | // types/mdx.d.ts 2 | declare module '*.mdx' { 3 | let MDXComponent: (props) => JSX.Element; 4 | export default MDXComponent; 5 | } 6 | -------------------------------------------------------------------------------- /website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /website/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { withDocz } from 'next-docz/config'; 2 | 3 | /** @type {import('next').NextConfig} */ 4 | export default withDocz({ 5 | reactStrictMode: true, 6 | pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'], 7 | trailingSlash: true, 8 | basePath: process.env.NEXT_PUBLIC_WEBSITE_BASE_PATH, 9 | }); 10 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "rm -rf .next && cd ../packages/react-naver-maps && pnpm link ../../website/node_modules/react; cd - && next dev", 7 | "build": "next build && next export", 8 | "start": "next start", 9 | "lint": "eslint src" 10 | }, 11 | "dependencies": { 12 | "@emotion/react": "^11.10.4", 13 | "@emotion/styled": "^11.10.4", 14 | "@mdx-js/react": "^2.1.5", 15 | "@next/font": "13.1.1", 16 | "@radix-ui/react-navigation-menu": "^1.1.1", 17 | "@types/node": "16.11.10", 18 | "@types/react": "18.0.26", 19 | "@types/react-dom": "18.0.10", 20 | "@types/react-syntax-highlighter": "^13.5.2", 21 | "next": "^12.3.1", 22 | "next-docz": "workspace:^0.0.1", 23 | "normalize.css": "^8.0.1", 24 | "react": "^18.2.0", 25 | "react-dom": "^18.2.0", 26 | "react-icons": "^4.7.1", 27 | "react-naver-maps": "workspace:^0.1.0", 28 | "react-syntax-highlighter": "^15.5.0", 29 | "typescript": "^4.8.4", 30 | "use-asset": "^1.0.4" 31 | }, 32 | "devDependencies": { 33 | "@types/gtag.js": "^0.0.12" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /website/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeakd/react-naver-maps/1de4aa19a58ef2a4044a3fd58448bba0134611e4/website/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /website/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeakd/react-naver-maps/1de4aa19a58ef2a4044a3fd58448bba0134611e4/website/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /website/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeakd/react-naver-maps/1de4aa19a58ef2a4044a3fd58448bba0134611e4/website/public/apple-touch-icon.png -------------------------------------------------------------------------------- /website/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /website/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeakd/react-naver-maps/1de4aa19a58ef2a4044a3fd58448bba0134611e4/website/public/favicon-16x16.png -------------------------------------------------------------------------------- /website/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeakd/react-naver-maps/1de4aa19a58ef2a4044a3fd58448bba0134611e4/website/public/favicon-32x32.png -------------------------------------------------------------------------------- /website/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeakd/react-naver-maps/1de4aa19a58ef2a4044a3fd58448bba0134611e4/website/public/favicon.ico -------------------------------------------------------------------------------- /website/public/google8aa2ebba48557029.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google8aa2ebba48557029.html -------------------------------------------------------------------------------- /website/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeakd/react-naver-maps/1de4aa19a58ef2a4044a3fd58448bba0134611e4/website/public/mstile-150x150.png -------------------------------------------------------------------------------- /website/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 43 | 57 | 75 | 89 | 110 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /website/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /website/src/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import * as NavMenu from '@radix-ui/react-navigation-menu'; 3 | import Link from 'next/link'; 4 | import type { ReactNode } from 'react'; 5 | import { FiChevronDown } from 'react-icons/fi'; 6 | 7 | import { menu } from '../menu'; 8 | 9 | type Props = { 10 | children?: ReactNode; 11 | }; 12 | 13 | export function Layout(props: Props) { 14 | return ( 15 |

16 |
30 |
36 | 37 |
38 |
45 |
46 | {props.children} 47 |
48 |
49 |
50 |
51 |
52 | ); 53 | } 54 | 55 | const navItemCss = css({ 56 | background: 'transparent', 57 | border: 'none', 58 | padding: '8px 12px', 59 | outline: 'none', 60 | userSelect: 'none', 61 | fontWeight: '500', 62 | lineHeight: 1, 63 | borderRadius: '4px', 64 | fontSize: '15px', 65 | 66 | display: 'flex', 67 | alignItems: 'center', 68 | ':hover': { backgroundColor: '#fafafa' }, 69 | // color: var(--violet11); 70 | }); 71 | 72 | function Header() { 73 | return ( 74 |
87 |
100 | 130 | 206 |
207 |
208 | ); 209 | } 210 | 211 | 212 | function Sidebar() { 213 | return ( 214 |
225 |
228 | 229 |
230 |
234 | {menu.map((item, idx) => { 235 | if (item.type === 'category') { 236 | return ( 237 |
238 |
239 | 240 | {item.name} 241 | 242 |
243 |
247 | {item.contents.map(({ name, href }) => { 248 | return ( 249 |
253 | 256 | {name} 262 | 263 |
264 | ); 265 | })} 266 |
267 |
) 268 | ; 269 | } 270 | 271 | return; 272 | })} 273 |
274 |
275 | ); 276 | } 277 | 278 | type MainProps = { 279 | children?: ReactNode; 280 | }; 281 | 282 | function Main(props: MainProps) { 283 | return ( 284 |
290 | {props.children} 291 |
292 | ); 293 | } 294 | -------------------------------------------------------------------------------- /website/src/hooks/useIsDarkMode.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function useIsDarkMode() { 4 | const [darkMode, setDarkMode] = useState(); 5 | 6 | useEffect(() => { 7 | const matchDark = window.matchMedia('(prefers-color-scheme: dark)'); 8 | 9 | setDarkMode(matchDark.matches); 10 | 11 | const darkHandler = (event: MediaQueryListEvent) => { 12 | setDarkMode(event.matches); 13 | }; 14 | matchDark.addEventListener('change', darkHandler); 15 | 16 | return () => { 17 | matchDark.removeEventListener('change', darkHandler); 18 | }; 19 | }, []); 20 | 21 | return darkMode; 22 | } 23 | -------------------------------------------------------------------------------- /website/src/lib/gtag.ts: -------------------------------------------------------------------------------- 1 | export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_TRACKING_ID || ''; 2 | 3 | type MappedTypeSnakeToCamel = {[K in keyof InputType as SnakeToCamelCase]: InputType[K]}; 4 | 5 | type SnakeToCamelCase = 6 | S extends `${infer T}_${infer U}` ? 7 | `${T}${Capitalize>}` : 8 | S; 9 | 10 | // https://developers.google.com/analytics/devguides/collection/gtagjs/pages 11 | export const pageview = (url: string) => { 12 | window.gtag('config', GA_TRACKING_ID, { page_location: url }); 13 | }; 14 | 15 | // https://developers.google.com/analytics/devguides/collection/gtagjs/events 16 | export const event = (action: Gtag.EventNames, { eventCategory, eventLabel, value }: MappedTypeSnakeToCamel) => { 17 | window.gtag('event', action, { 18 | event_category: eventCategory, 19 | event_label: eventLabel, 20 | value: value, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /website/src/menu.ts: -------------------------------------------------------------------------------- 1 | 2 | export const menu = [ 3 | { 4 | type: 'category', 5 | name: 'Guides', 6 | contents: [ 7 | { 8 | name: 'Introduction', 9 | href: '/', 10 | }, 11 | { 12 | name: 'Quickstart', 13 | href: '/guides/quickstart', 14 | }, 15 | { 16 | name: 'Core concepts', 17 | href: '/guides/core-concepts', 18 | }, 19 | { 20 | name: 'Customize overlays', 21 | href: '/guides/customize-overlays', 22 | }, 23 | { 24 | name: 'Suspensed useNavermaps', 25 | href: '/guides/suspensed-use-navermaps', 26 | }, 27 | { 28 | name: 'Migration guide from v0.0', 29 | href: '/guides/migration-guide-from-0.0', 30 | }, 31 | ], 32 | }, 33 | { 34 | type: 'category', 35 | name: 'Examples', 36 | contents: [ 37 | { 38 | name: '지도 기본 예제', 39 | href: '/examples/map-tutorial-1-simple', 40 | }, 41 | { 42 | name: '지도 옵션 조정하기', 43 | href: '/examples/map-tutorial-2-options', 44 | }, 45 | { 46 | name: '지도 유형 설정하기', 47 | href: '/examples/map-tutorial-3-types', 48 | }, 49 | { 50 | name: '지도 좌표 경계 확인하기', 51 | href: '/examples/map-tutorial-4-bounds', 52 | }, 53 | { 54 | name: '지도 이동하기', 55 | href: '/examples/map-tutorial-5-moves', 56 | }, 57 | { 58 | name: 'HTML5 Geolocation API \n활용하기', 59 | href: '/examples/map-tutorial-6-geolocation', 60 | }, 61 | { 62 | name: '마커 표시하기', 63 | href: '/examples/marker-tutorial-1-simple', 64 | }, 65 | { 66 | name: '마커 클러스터화하기', 67 | href: '/examples/marker-cluster-tutorial', 68 | }, 69 | { 70 | name: '사용자 정의 컨트롤 만들기', 71 | href: '/examples/control-tutorial-4-custom-p1', 72 | }, 73 | ], 74 | }, 75 | { 76 | type: 'category', 77 | name: 'API Reference', 78 | contents: [ 79 | { 80 | name: 'NavermapsProvider', 81 | href: '/api-references/navermaps-provider', 82 | }, 83 | { 84 | name: 'NaverMap', 85 | href: '/api-references/naver-map', 86 | }, 87 | { 88 | name: 'Container', 89 | href: '/api-references/container', 90 | }, 91 | { 92 | name: 'Circle', 93 | href: '/api-references/circle', 94 | }, 95 | { 96 | name: 'Ellipse', 97 | href: '/api-references/ellipse', 98 | }, 99 | { 100 | name: 'GroundOverlay', 101 | href: '/api-references/ground-overlay', 102 | }, 103 | { 104 | name: 'InfoWindow', 105 | href: '/api-references/info-window', 106 | }, 107 | { 108 | name: 'Marker', 109 | href: '/api-references/marker', 110 | }, 111 | { 112 | name: 'Polygon', 113 | href: '/api-references/polygon', 114 | }, 115 | { 116 | name: 'Polyline', 117 | href: '/api-references/polyline', 118 | }, 119 | { 120 | name: 'Rectangle', 121 | href: '/api-references/rectangle', 122 | }, 123 | { 124 | name: 'useNavermaps', 125 | href: '/api-references/use-navermaps', 126 | }, 127 | { 128 | name: 'useListener', 129 | href: '/api-references/use-listener', 130 | }, 131 | { 132 | name: 'Listener', 133 | href: '/api-references/listener', 134 | }, 135 | { 136 | name: 'useMap', 137 | href: '/api-references/use-map', 138 | }, 139 | { 140 | name: 'loadNavermapsScript', 141 | href: '/api-references/load-navermaps-script', 142 | }, 143 | ], 144 | }, 145 | ] as const; 146 | -------------------------------------------------------------------------------- /website/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import 'normalize.css'; 2 | import { css, Global } from '@emotion/react'; 3 | import { MDXProvider } from '@mdx-js/react'; 4 | import type { AppProps } from 'next/app'; 5 | import Head from 'next/head'; 6 | import Link from 'next/link'; 7 | import { useRouter } from 'next/router'; 8 | import Script from 'next/script'; 9 | import { ComponentPropsWithoutRef, useEffect } from 'react'; 10 | import { FiExternalLink } from 'react-icons/fi'; 11 | import { NavermapsProvider } from 'react-naver-maps'; 12 | import { Prism } from 'react-syntax-highlighter'; 13 | import { materialLight } from 'react-syntax-highlighter/dist/cjs/styles/prism'; 14 | 15 | import { Layout } from '../components/layout'; 16 | import * as gtag from '../lib/gtag'; 17 | 18 | const WEBSITE_BASE_PATH = process.env.NEXT_PUBLIC_WEBSITE_BASE_PATH || ''; 19 | 20 | // import { useIsDarkMode } from '../hooks/useIsDarkMode'; 21 | 22 | function Code({ className, ...props }: ComponentPropsWithoutRef<'code'>) { 23 | // const isDarkMode = useIsDarkMode(); 24 | 25 | const match = /language-(\w+)/.exec(className || ''); 26 | return match 27 | ? ( 28 | 32 | ) 33 | : ( 34 | 43 | 46 | 47 | ); 48 | } 49 | 50 | function Anchor({ href, ...restProps }: ComponentPropsWithoutRef<'a'>) { 51 | const isExternal = /https?:\/\//.test(href || ''); 52 | 53 | if (isExternal) { 54 | return ( 55 | 56 | 61 | 62 | 63 | ); 64 | } 65 | 66 | return ( 67 | 68 | 69 | 74 | 75 | 76 | ); 77 | } 78 | 79 | function UL(props: ComponentPropsWithoutRef<'ul'>) { 80 | return ( 81 |
    82 | ); 83 | } 84 | 85 | const mdxComponents = { code: Code, a: Anchor, ul: UL }; 86 | 87 | function App({ Component, pageProps }: AppProps) { 88 | const router = useRouter(); 89 | useEffect(() => { 90 | const handleRouteChange = (url: string) => { 91 | gtag.pageview(url); 92 | }; 93 | router.events.on('routeChangeComplete', handleRouteChange); 94 | return () => { 95 | router.events.off('routeChangeComplete', handleRouteChange); 96 | }; 97 | }, [router.events]); 98 | 99 | return ( 100 | <> 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | React Naver Maps 111 | 112 | {/* Global Site Tag (gtag.js) - Google Analytics */} 113 | 128 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | ); 153 | } 154 | 155 | export default App; 156 | -------------------------------------------------------------------------------- /website/src/pages/api-references/circle.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { Circle } from 'react-naver-maps' 3 | 4 | # Circle 5 | 6 | [네이버 공식문서 Circle](https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Circle.html) 7 | 8 | ## Props 9 | 10 | -------------------------------------------------------------------------------- /website/src/pages/api-references/container.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { Container } from 'react-naver-maps' 3 | 4 | # Container 5 | 6 | 맵이나 파노라마가 렌더되는 컴포넌트입니다. div 역할을 하며 SSR처리와 Suspense가 내장되어있습니다. 7 | 8 | ## Props 9 | 10 | 15 | -------------------------------------------------------------------------------- /website/src/pages/api-references/ellipse.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { Ellipse } from 'react-naver-maps' 3 | 4 | # Ellipse 5 | 6 | [네이버 공식문서 Ellipse](https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Ellipse.html) 7 | 8 | ## Props 9 | 10 | -------------------------------------------------------------------------------- /website/src/pages/api-references/ground-overlay.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { GroundOverlay } from 'react-naver-maps' 3 | 4 | # GroundOverlay 5 | 6 | [네이버 공식문서 GroundOverlay](https://navermaps.github.io/maps.js.ncp/docs/naver.maps.GroundOverlay.html) 7 | 8 | ## Props 9 | 10 | -------------------------------------------------------------------------------- /website/src/pages/api-references/info-window.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { InfoWindow } from 'react-naver-maps' 3 | 4 | # InfoWindow 5 | 6 | [네이버 공식문서 InfoWindow](https://navermaps.github.io/maps.js.ncp/docs/naver.maps.InfoWindow.html) 7 | 8 | ## Props 9 | 10 | -------------------------------------------------------------------------------- /website/src/pages/api-references/listener.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz' 2 | import { Listener } from 'react-naver-maps' 3 | 4 | # Listener 5 | 6 | [Customize overlays](/guides/customize-overlays)를 참고해주세요 7 | 8 | ## Options 9 | 10 | 11 | 12 | ## Examples 13 | 14 | ``` jsx 15 | 16 | {console.log('click') }} /> 17 | 18 | 19 | // standalone 20 | {console.log('click') }} /> 21 | ``` -------------------------------------------------------------------------------- /website/src/pages/api-references/load-navermaps-script.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { loadNavermapsScript } from 'react-naver-maps' 3 | 4 | # loadNavermapsScript 5 | 6 | ## Options 7 | 8 | -------------------------------------------------------------------------------- /website/src/pages/api-references/marker.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { Marker } from 'react-naver-maps' 3 | 4 | # Marker 5 | 6 | [네이버 공식문서 Marker](https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Marker.html) 7 | 8 | ## Props 9 | 10 | -------------------------------------------------------------------------------- /website/src/pages/api-references/naver-map.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { NaverMap } from 'react-naver-maps' 3 | 4 | # NaverMap 5 | 6 | [네이버 공식문서 Map](https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Map.html) 7 | 8 | ## Props 9 | 10 | -------------------------------------------------------------------------------- /website/src/pages/api-references/navermaps-provider.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { NavermapsProvider } from 'react-naver-maps' 3 | 4 | # NavermapsProvider 5 | 6 | [네이버 공식 문서 서브 모듈 시스템](https://navermaps.github.io/maps.js.ncp/docs/tutorial-4-Submodules.html) 7 | 8 | ## Props 9 | 10 | 11 | 12 | ## Example 13 | 14 | ``` jsx 15 | 19 | 20 | 21 | ``` -------------------------------------------------------------------------------- /website/src/pages/api-references/ploygon.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { Polygon } from 'react-naver-maps' 3 | 4 | # Polygon 5 | 6 | ## Props 7 | 8 | -------------------------------------------------------------------------------- /website/src/pages/api-references/polygon.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { Polygon } from 'react-naver-maps' 3 | 4 | # Polygon 5 | 6 | [네이버 공식문서 Polygon](https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Polygon.html) 7 | 8 | ## Props 9 | 10 | -------------------------------------------------------------------------------- /website/src/pages/api-references/polyline.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { Polyline } from 'react-naver-maps' 3 | 4 | # Polyline 5 | 6 | [네이버 공식문서 Polyline](https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Polyline.html) 7 | 8 | ## Props 9 | 10 | -------------------------------------------------------------------------------- /website/src/pages/api-references/rectangle.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz'; 2 | import { Rectangle } from 'react-naver-maps' 3 | 4 | # Rectangle 5 | 6 | [네이버 공식문서 Rectangle](https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Rectangle.html) 7 | 8 | ## Props 9 | 10 | -------------------------------------------------------------------------------- /website/src/pages/api-references/use-listener.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz' 2 | import { useListener } from 'react-naver-maps' 3 | 4 | # useListener 5 | 6 | [Customize overlays](/guides/customize-overlays)를 참고해주세요 7 | 8 | ## Example 9 | 10 | ``` jsx 11 | function MyMarker() { 12 | ... 13 | useListener(marker, 'click', () => console.log('click')) 14 | ... 15 | } 16 | ``` -------------------------------------------------------------------------------- /website/src/pages/api-references/use-map.mdx: -------------------------------------------------------------------------------- 1 | import { Props } from 'next-docz' 2 | import { useMap } from 'react-naver-maps' 3 | 4 | # useMap 5 | 6 | [Customize overlays](/guides/customize-overlays)를 참고해주세요 7 | 8 | ## Example 9 | 10 | ``` jsx 11 | function MyControls() { 12 | ... 13 | const map = useMap() 14 | ... 15 | } 16 | ``` -------------------------------------------------------------------------------- /website/src/pages/api-references/use-navermaps.mdx: -------------------------------------------------------------------------------- 1 | import { useNavermaps } from 'react-naver-maps' 2 | 3 | # useNavermaps 4 | 5 | [NavermapProvider](/api-references/navermaps-provider) 설정이 필요합니다. 6 | 7 | ## Example 8 | 9 | ``` jsx 10 | function MyComponent() { 11 | const navermaps = useNavermaps(); 12 | // navermaps === window.naver.maps; 13 | 14 | return null; 15 | } 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | -------------------------------------------------------------------------------- /website/src/pages/examples/control-tutorial-4-custom-p1.mdx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import { Playground, Props } from 'next-docz' 3 | import { Container as MapDiv, NaverMap, useNavermaps, Overlay, useMap } from 'react-naver-maps' 4 | 5 | # 사용자 정의 컨트롤 만들기 6 | 7 | 네이버지도 공식 튜토리얼 [사용자 정의 컨트롤 만들기](https://navermaps.github.io/maps.js.ncp/docs/tutorial-4-control-custom-p1.example.html)의 구현 예시입니다. 8 | 9 | 16 | {() => { 17 | function MyCustomControl() { 18 | const locationBtnHtml = ` 19 | 38 | NAVER 그린팩토리 54 | 55 | `; 56 | const navermaps = useNavermaps(); 57 | const map = useMap(); 58 | // customControl 객체 이용하기 59 | // Customize Overlay 참고 60 | // https://zeakd.github.io/react-naver-maps/guides/customize-overlays/ 61 | const [customControl1] = useState(() => { 62 | return new navermaps.CustomControl(locationBtnHtml, { 63 | position: navermaps.Position.TOP_LEFT 64 | }); 65 | }) 66 | 67 | useEffect(() => { 68 | // naver.maps.Event.addDOMListener 사용할 필요 없이, native addEventListener를 사용합니다. 69 | // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener 70 | const domElement = customControl1.getElement(); 71 | const domListener = () => { 72 | map.setCenter(new navermaps.LatLng(37.3595953, 127.1053971)); 73 | }; 74 | 75 | domElement.addEventListener('click', domListener) 76 | 77 | return () => { 78 | domElement.removeEventListener('click', domListener); 79 | } 80 | }, []) 81 | 82 | useEffect(() => { 83 | // Map 객체의 controls 활용하기 84 | // Jquery 없이 생성하기 85 | // var $locationBtn = $(locationBtnHtml), 86 | // locationBtnEl = $locationBtn[0]; 87 | const parent = document.createElement('div'); 88 | parent.innerHTML = locationBtnHtml; 89 | const locationBtnEl = parent.children[0] 90 | 91 | map.controls[naver.maps.Position.LEFT_CENTER].push(locationBtnEl); 92 | 93 | // naver.maps.Event.addDOMListener 사용할 필요 없이, native addEventListener를 사용합니다. 94 | // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener 95 | const domListener = () => { 96 | map.setCenter(new navermaps.LatLng(37.3595953, 127.1053971)); 97 | }; 98 | 99 | locationBtnEl.addEventListener('click', domListener) 100 | 101 | return () => { 102 | locationBtnEl.removeEventListener('click', domListener); 103 | } 104 | }, []) 105 | 106 | return ( 107 | 108 | ); 109 | } 110 | 111 | function MyMap() { 112 | const [init, setInit] = useState(false); 113 | 114 | return ( 115 | { 118 | setInit(true); 119 | }} 120 | > 121 | {init && } 122 | 123 | ) 124 | } 125 | 126 | return ( 127 | 131 | 132 | 133 | ) 134 | }} 135 | 136 | -------------------------------------------------------------------------------- /website/src/pages/examples/map-tutorial-1-simple.mdx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react' 2 | import { Playground, Props } from 'next-docz' 3 | import { Container as MapDiv, NaverMap } from 'react-naver-maps' 4 | 5 | # 지도 기본 예제 6 | 7 | 네이버지도 공식 튜토리얼 [지도 기본 예제](https://navermaps.github.io/maps.js.ncp/docs/tutorial-1-map-simple.example.html)의 구현 예시입니다. 8 | 9 | 15 | 19 | 20 | 21 | 22 | 23 | 3 -------------------------------------------------------------------------------- /website/src/pages/examples/map-tutorial-2-options.mdx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { Playground, Props } from 'next-docz' 3 | import { Container as MapDiv, NaverMap, useNavermaps } from 'react-naver-maps'; 4 | 5 | 6 | # 지도 옵션 조정하기 7 | 8 | 네이버지도 공식 튜토리얼 [지도 옵션 조정하기](https://navermaps.github.io/maps.js.ncp/docs/tutorial-2-map-options.example.html)의 구현 예시입니다. 9 | 10 | 16 | 21 | {() => { 22 | const navermaps = useNavermaps(); 23 | 24 | const [zoom, setZoom] = useState(13); 25 | 26 | const [draggable, setDraggable] = useState(true); 27 | const [disableKineticPan, setDisableKineticPan] = useState(true); 28 | const [tileTransition, setTileTransition] = useState(true); 29 | const [minZoom, setMinZoom] = useState(7); 30 | const [scaleControl, setScaleControl] = useState(true); 31 | 32 | const handleZoomChanged = useCallback((zoom) => { 33 | console.log(`zoom: ${zoom}`) 34 | }, []) 35 | 36 | const normalBtnStyle = { 37 | backgroundColor: '#fff', 38 | border: 'solid 1px #333', 39 | outline: '0 none', 40 | borderRadius: '5px', 41 | boxShadow: '2px 2px 1px 1px rgba(0, 0, 0, 0.5)', 42 | margin: '0 5px 5px 0', 43 | } 44 | 45 | const selectedBtnStyle = { 46 | ...normalBtnStyle, 47 | backgroundColor: '#2780E3', 48 | color: 'white', 49 | } 50 | 51 | return ( 52 | <> 53 |
    60 | 66 | 72 | 78 | 84 | 90 |
    91 | 127 | 128 | ); 129 | }} 130 |
    131 |
    132 | 133 | https://navermaps.github.io/maps.js.ncp/docs/tutorial-2-map-options.example.html -------------------------------------------------------------------------------- /website/src/pages/examples/map-tutorial-3-types.mdx: -------------------------------------------------------------------------------- 1 | import { Playground, Props } from 'next-docz'; 2 | import { useState, useRef } from 'react'; 3 | import { Container as MapDiv, NaverMap, useNavermaps } from 'react-naver-maps'; 4 | 5 | # 지도 유형 설정하기 6 | 7 | 네이버지도 공식 튜토리얼 [지도 유형 설정하기](https://navermaps.github.io/maps.js.ncp/docs/tutorial-3-map-types.example.html)의 구현 예시입니다. 8 | 9 | 15 | 20 | {() => { 21 | const navermaps = useNavermaps(); 22 | const [mapTypeId, setMapTypeId] = useState(navermaps.MapTypeId.NORMAL); 23 | const buttons = [ 24 | { 25 | typeId: navermaps.MapTypeId.NORMAL, 26 | text: '일반지도' 27 | }, 28 | { 29 | typeId: navermaps.MapTypeId.TERRAIN, 30 | text: '지형도' 31 | }, 32 | { 33 | typeId: navermaps.MapTypeId.SATELLITE, 34 | text: '위성지도' 35 | }, 36 | { 37 | typeId: navermaps.MapTypeId.HYBRID, 38 | text: '겹쳐보기' 39 | }, 40 | ] 41 | 42 | return ( 43 | <> 44 |
    51 | {buttons.map(btn => { 52 | return 67 | })} 68 |
    69 | 77 | 78 | ); 79 | }} 80 |
    81 |
    82 | -------------------------------------------------------------------------------- /website/src/pages/examples/map-tutorial-4-bounds.mdx: -------------------------------------------------------------------------------- 1 | import { Playground, Props } from 'next-docz' 2 | import { useState, useEffect, useRef, useLayoutEffect } from 'react' 3 | import { Container as MapDiv, NaverMap, useNavermaps, Rectangle, useMap } from 'react-naver-maps' 4 | 5 | # 지도 좌표 경계 확인하기 6 | 7 | 네이버지도 공식 튜토리얼 [지도 유형 설정하기](https://navermaps.github.io/maps.js.ncp/docs/tutorial-1-map-simple.example.html)의 구현 예시입니다. 8 | 9 | 16 | {() => { 17 | const buttonStyle = { 18 | position: 'absolute', 19 | top: 10, 20 | left: 10, 21 | zIndex: 1000, 22 | backgroundColor: '#fff', 23 | border: 'solid 1px #333', 24 | outline: '0 none', 25 | borderRadius: '5px', 26 | boxShadow: '2px 2px 1px 1px rgba(0, 0, 0, 0.5)', 27 | margin: '0 5px 5px 0', 28 | } 29 | 30 | function MyMap() { 31 | const navermaps = useNavermaps(); 32 | const center = new navermaps.LatLng(37.5666805, 126.9784147); 33 | const dokdo = new navermaps.LatLngBounds( 34 | new navermaps.LatLng(37.2380651, 131.8562652), 35 | new navermaps.LatLng(37.2444436, 131.8786475)); 36 | 37 | const [map, setMap] = useState(null); 38 | const [rect, setRect] = useState(null); 39 | 40 | // map과 rect가 처음 mount되었을 때에만 동작합니다. 41 | useLayoutEffect(() => { 42 | if (map && rect) { 43 | rect.setBounds(map.getBounds()); 44 | } 45 | }, [map, rect]) 46 | 47 | return { 57 | if (rect) { 58 | window.setTimeout(function() { 59 | rect.setBounds(bounds); 60 | }, 500); 61 | } 62 | }} 63 | > 64 | 71 | 80 | 81 | } 82 | 83 | return ( 84 | 89 | 90 | 91 | ); 92 | }} 93 | 94 | -------------------------------------------------------------------------------- /website/src/pages/examples/map-tutorial-5-moves.mdx: -------------------------------------------------------------------------------- 1 | import { Playground, Props } from 'next-docz'; 2 | import { useState, useRef } from 'react'; 3 | import { Container as MapDiv, NaverMap, useNavermaps } from 'react-naver-maps'; 4 | 5 | # 지도 이동하기 6 | 7 | 네이버지도 공식 튜토리얼 [지도 이동하기](https://navermaps.github.io/maps.js.ncp/docs/tutorial-5-map-moves.example.html)의 구현 예시입니다. 8 | 9 | 16 | {() => { 17 | 18 | const buttonsStyle = { 19 | position: 'absolute', 20 | top: 0, 21 | left: 0, 22 | zIndex: 1000, 23 | padding: '5px', 24 | }; 25 | 26 | const buttonStyle = { 27 | margin: '0 5px 5px 0', 28 | WebkitAppearance: 'button', 29 | cursor: 'pointer', 30 | color: '#555', 31 | padding: '2px 6px', 32 | background: '#fff', 33 | border: 'solid 1px #333', 34 | cursor: 'pointer', 35 | WebkitBorderRadius: '5px', 36 | outline: '0 none', 37 | borderRadius: '5px', 38 | boxShadow: '2px 2px 1px 1px rgba(0, 0, 0, 0.5)', 39 | }; 40 | 41 | function MyMap() { 42 | const navermaps = useNavermaps(); 43 | 44 | const jeju = new navermaps.LatLng(33.3590628, 126.534361); 45 | const busan = new navermaps.LatLng(35.1797865, 129.0750194); 46 | const dokdo = new navermaps.LatLngBounds( 47 | new navermaps.LatLng(37.2380651, 131.8562652), 48 | new navermaps.LatLng(37.2444436, 131.8786475)); 49 | const seoul = new navermaps.LatLngBounds( 50 | new navermaps.LatLng(37.42829747263545, 126.76620435615891), 51 | new navermaps.LatLng(37.7010174173061, 127.18379493229875)); 52 | 53 | const center = new navermaps.LatLng(37.5666805, 126.9784147); 54 | 55 | // useRef 대신 useState를 통해 ref를 가져옵니다. 56 | const [map, setMap] = useState(null); 57 | 58 | return 64 | {/* buttons */} 65 |
    66 | 75 | 84 | 93 | 102 | 111 | 120 |
    121 |
    122 | } 123 | 124 | return ( 125 | 130 | 131 | 132 | ); 133 | }} 134 |
    135 | -------------------------------------------------------------------------------- /website/src/pages/examples/map-tutorial-6-geolocation.mdx: -------------------------------------------------------------------------------- 1 | import { Playground, Props } from 'next-docz'; 2 | import { useState, useRef, useEffect } from 'react'; 3 | import { Container as MapDiv, NaverMap, useNavermaps, InfoWindow } from 'react-naver-maps'; 4 | 5 | # HTML5 Geolocation API 활용하기 6 | 7 | 네이버지도 공식 튜토리얼 [HTML5 Geolocation API 활용하기](https://navermaps.github.io/maps.js.ncp/docs/tutorial-6-map-geolocation.example.html)의 구현 예시입니다. 8 | 9 | 16 | {() => { 17 | function MyMap() { 18 | const navermaps = useNavermaps(); 19 | 20 | // useRef 대신 useState를 통해 ref를 가져옵니다. 21 | const [map, setMap] = useState(null); 22 | const [infowindow, setInfoWindow] = useState(null); 23 | 24 | 25 | function onSuccessGeolocation(position) { 26 | if (!map || !infowindow) return 27 | 28 | const location = new navermaps.LatLng(position.coords.latitude, 29 | position.coords.longitude); 30 | map.setCenter(location); 31 | map.setZoom(10); 32 | infowindow.setContent('
    ' + 'geolocation.getCurrentPosition() 위치' + '
    '); 33 | infowindow.open(map, location); 34 | console.log('Coordinates: ' + location.toString()); 35 | } 36 | 37 | function onErrorGeolocation() { 38 | if (!map || !infowindow) return 39 | 40 | const center = map.getCenter(); 41 | infowindow.setContent('
    ' + 42 | '
    Geolocation failed!
    '+ "latitude: "+ center.lat() +"
    longitude: "+ center.lng() +'
    '); 43 | infowindow.open(map, center); 44 | 45 | if (navigator.geolocation) { 46 | navigator.geolocation.getCurrentPosition(onSuccessGeolocation, onErrorGeolocation); 47 | } else { 48 | const center = map.getCenter(); 49 | infowindow.setContent('
    Geolocation not supported
    '); 50 | infowindow.open(map, center); 51 | } 52 | } 53 | 54 | useEffect(() => { 55 | if (!map || !infowindow) { 56 | return; 57 | } 58 | 59 | if (navigator.geolocation) { 60 | navigator.geolocation.getCurrentPosition(onSuccessGeolocation, onErrorGeolocation); 61 | } else { 62 | var center = map.getCenter(); 63 | infowindow.setContent('
    Geolocation not supported
    '); 64 | infowindow.open(map, center); 65 | } 66 | }, [map, infowindow]); 67 | 68 | 69 | return 76 | 77 | 78 | } 79 | 80 | return ( 81 | 86 | 87 | 88 | ); 89 | }} 90 |
    91 | -------------------------------------------------------------------------------- /website/src/pages/examples/marker-cluster-tutorial.mdx: -------------------------------------------------------------------------------- 1 | import { Suspense, useLayoutEffect, useEffect, useState } from 'react' 2 | import { Playground, Props } from 'next-docz' 3 | import { Container as MapDiv, NaverMap, Marker, useNavermaps, Overlay, useMap } from 'react-naver-maps' 4 | import { makeMarkerClustering } from '../../samples/marker-cluster' 5 | import { accidentDeath } from '../../samples/accident-death' 6 | 7 | # 마커 클러스터화하기 8 | 9 | 네이버지도 공식 튜토리얼 [마커 클러스터화하기](https://navermaps.github.io/maps.js.ncp/docs/tutorial-marker-cluster.example.html)의 구현 예시입니다. 10 | 11 | 16 | {() => { 17 | function MarkerCluster() { 18 | // https://github.com/navermaps/marker-tools.js/blob/master/marker-clustering/src/MarkerClustering.js 19 | // 예제에서 제공된 코드를 그대로 사용하되 naver 객체를 주입 받도록 간단히 makeMarkerClustering로 Wrapping 합니다. 20 | 21 | const navermaps = useNavermaps(); 22 | const map = useMap(); 23 | 24 | // https://github.com/zeakd/react-naver-maps/blob/main/website/src/samples/marker-cluster.js 25 | const MarkerClustering = makeMarkerClustering(window.naver); 26 | 27 | const htmlMarker1 = { 28 | content: '
    ', 29 | size: navermaps.Size(40, 40), 30 | anchor: navermaps.Point(20, 20) 31 | } 32 | const htmlMarker2 = { 33 | content: '
    ', 34 | size: navermaps.Size(40, 40), 35 | anchor: navermaps.Point(20, 20) 36 | } 37 | const htmlMarker3 = { 38 | content: '
    ', 39 | size: navermaps.Size(40, 40), 40 | anchor: navermaps.Point(20, 20) 41 | } 42 | const htmlMarker4 = { 43 | content: '
    ', 44 | size: navermaps.Size(40, 40), 45 | anchor: navermaps.Point(20, 20) 46 | } 47 | const htmlMarker5 = { 48 | content: '
    ', 49 | size: navermaps.Size(40, 40), 50 | anchor: navermaps.Point(20, 20) 51 | }; 52 | 53 | // https://navermaps.github.io/maps.js.ncp/docs/data/accidentdeath.js 54 | const data = accidentDeath.searchResult.accidentDeath; 55 | 56 | // Customize Overlay 참고 57 | // https://zeakd.github.io/react-naver-maps/guides/customize-overlays/ 58 | const [cluster] = useState(() => { 59 | const markers = []; 60 | 61 | for (var i = 0, ii = data.length; i < ii; i++) { 62 | var spot = data[i], 63 | latlng = new naver.maps.LatLng(spot.grd_la, spot.grd_lo), 64 | marker = new naver.maps.Marker({ 65 | position: latlng, 66 | draggable: true 67 | }); 68 | 69 | markers.push(marker); 70 | } 71 | 72 | const cluster = new MarkerClustering({ 73 | minClusterSize: 2, 74 | maxZoom: 8, 75 | map: map, 76 | markers: markers, 77 | disableClickZoom: false, 78 | gridSize: 120, 79 | icons: [htmlMarker1, htmlMarker2, htmlMarker3, htmlMarker4, htmlMarker5], 80 | indexGenerator: [10, 100, 200, 500, 1000], 81 | stylingFunction: function(clusterMarker, count) { 82 | // without jquery $(clusterMarker.getElement()).find('div:first-child').text(count) 83 | clusterMarker.getElement().querySelector('div:first-child').innerText = count; 84 | } 85 | 86 | }); 87 | 88 | return cluster; 89 | }) 90 | 91 | return ( 92 | 93 | ); 94 | } 95 | 96 | function MyMap() { 97 | const navermaps = useNavermaps(); 98 | 99 | return ( 100 | 109 | 110 | 111 | ) 112 | } 113 | 114 | return ( 115 | 121 | 122 | 123 | ); 124 | }} 125 |
    -------------------------------------------------------------------------------- /website/src/pages/examples/marker-tutorial-1-simple.mdx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react' 2 | import { Playground, Props } from 'next-docz' 3 | import { Container as MapDiv, NaverMap, Marker, useNavermaps } from 'react-naver-maps' 4 | 5 | # 지도 기본 예제 6 | 7 | 네이버지도 공식 튜토리얼 [마커 표시하기](https://navermaps.github.io/maps.js.ncp/docs/tutorial-1-marker-simple.example.html)의 구현 예시입니다. 8 | 9 | 15 | {() => { 16 | function MyMap() { 17 | // instead of window.naver.maps 18 | const navermaps = useNavermaps(); 19 | 20 | return ( 21 | 25 | 28 | 29 | ); 30 | } 31 | 32 | return ( 33 | 37 | 38 | 39 | ); 40 | }} 41 | 42 | 43 | -------------------------------------------------------------------------------- /website/src/pages/guides/controlled-tutorial.mdx: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /website/src/pages/guides/core-concepts.mdx: -------------------------------------------------------------------------------- 1 | # Core concepts 2 | 3 | React Naver Maps는 최대한 Naver Maps의 기본 기능을 유지하되 몇가지 React 에서 다루기 용이한 몇가지 기능을 제공합니다. 4 | 5 | ### React Component interface 6 | 7 | Naver Map의 요소들을 React Component형태로 mount/unmount 할 수 있으며 children을 통해 렌더여부를 결정할 수 있습니다. 8 | 9 | ``` jsx 10 | import { NaverMap, Marker } from 'react-naver-maps' 11 | 12 | 13 | 16 | 17 | ``` 18 | 19 | ### Better event handler 20 | 21 | React의 EventHandler 방식을 사용하여 더 쉽게 Naver Maps 의 이벤트를 다룰 수 있습니다. 22 | 23 | ``` js 24 | // listener를 추가할 때 25 | map.addListener('zoom_changed', zoomListener); // 또는 26 | window.naver.maps.Event.addListener(map, 'zoom_changed', zoomListener) 27 | 28 | // listener를 제거할 때 29 | map.removeListener(zoomListener); // 또는 30 | window.naver.maps.Event.removeListener(zoomListener) 31 | ``` 32 | 33 | ``` jsx 34 | // props로 전달됩니다. 35 | 38 | 39 | // 간단히 props에서 제거합니다. 40 | 43 | ``` 44 | 45 | ### Controlled KVO Component 46 | 47 | [Controlled Component](https://reactjs.org/docs/forms.html#controlled-components) 방식을 사용하여 단방향 제어가 가능합니다. 48 | 49 | ``` jsx 50 | import { NaverMap } from 'react-naver-maps'; 51 | 52 | function MyMap() { 53 | const [center, setCenter] = useState([37.3595704, 127.105399]); 54 | 55 | return ( 56 | <> 57 | { 60 | setCenter(map.getCenter()); 61 | }} 62 | /> 63 | 66 | 67 | ) 68 | } 69 | 70 | ``` -------------------------------------------------------------------------------- /website/src/pages/guides/customize-overlays.mdx: -------------------------------------------------------------------------------- 1 | import { Playground } from 'next-docz'; 2 | import { useState, useRef } from 'react'; 3 | import { 4 | Container as MapDiv, 5 | NaverMap, 6 | useNavermaps, 7 | Overlay, 8 | useListener, 9 | Listener, 10 | useMap, 11 | } from 'react-naver-maps'; 12 | 13 | # Customize Overlays 14 | 15 | 복잡한 로직을 다룰 수 있도록 직접 Overlay를 생성할 수 있는 여러 유틸리티를 제공하고 있습니다. 16 | 17 | 29 | {() => { 30 | function MyMarkers() { 31 | const navermaps = useNavermaps(); 32 | 33 | // 마커를 한번만 생성하기 위해 useState lazy initialize 사용 34 | const [marker1] = useState(() => new navermaps.Marker({ 35 | position: { lat: 37.5666103, lng: 126.9783882 }, 36 | })); 37 | 38 | // 마커를 한번만 생성하기 위해 useRef 사용 39 | const marker2Ref = useRef(null); 40 | if (!marker2Ref.current) { 41 | marker2Ref.current = new navermaps.Marker({ 42 | position: { lat: 37.5657259, lng: 126.97547 }, 43 | }) 44 | } 45 | const marker2 = marker2Ref.current; 46 | 47 | // hook 으로 이벤트 리스너 등록 48 | useListener(marker1, 'click', () => window.alert('서울시청 click')) 49 | 50 | return ( 51 | <> 52 | 53 | 54 | {/* Component 로 이벤트 리스너 등록 */} 55 | window.alert('덕수궁 click')} /> 56 | 57 | 58 | ); 59 | } 60 | 61 | function Buttons() { 62 | // Map의 instance를 가져옵니다. 63 | const naverMap = useMap(); 64 | 65 | return ( 66 |
    71 | 77 | 84 |
    85 | ); 86 | } 87 | 88 | return ( 89 | 95 | 96 | 97 | 98 | 99 | 100 | ) 101 | }} 102 |
    -------------------------------------------------------------------------------- /website/src/pages/guides/for-performance.mdx: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /website/src/pages/guides/installation.mdx: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Prerequisites 4 | 5 | 시작에 앞서 Naver Maps 클라이언트 아이디 발급이 필요합니다. [공식문서](https://navermaps.github.io/maps.js.ncp/docs/tutorial-1-Getting-Client-ID.html)를 참고하여 6 | 클라이언트 아이디 발급을 진행해주세요. 7 | 8 | ## Add React Naver Maps 9 | 10 | ``` bash 11 | npm install react-naver-maps@next 12 | ``` 13 | -------------------------------------------------------------------------------- /website/src/pages/guides/introduction.mdx: -------------------------------------------------------------------------------- 1 | import { Playground } from 'next-docz' 2 | import { Container as MapDiv, NaverMap, Marker } from 'react-naver-maps' 3 | 4 | # React Naver Maps 5 | 6 | React Naver Maps 는 [Naver Maps](https://navermaps.github.io/maps.js.ncp/)의 Non-Official React binding 라이브러리입니다. 7 | 8 | 9 | 15 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | - [Quick Start](/guides/quickstart) 29 | - [Core Conepts](/guides/core-concepts) 30 | - [Migration guide from v0.0](/guides/migration-guide-from-0.0) 31 | - [Customize Overlays](/guides/customize-overlays) 32 | -------------------------------------------------------------------------------- /website/src/pages/guides/migration-guide-from-0.0.mdx: -------------------------------------------------------------------------------- 1 | # Migration Guide from v0.0 2 | 3 | ## Removed `` 4 | 5 | 기존 RenderAfterNavermapsLoaded 는 SSR 문제와 컴포넌트 렌더 시의 모호함이 있었고 v0.1 에서 제거되었습니다. 6 | 대신, Application Root 에 ``를 추가해주세요 7 | 8 | ``` jsx 9 | // v0.0 (X) 10 | import { RenderAfterNavermapsLoaded } from 'react-naver-maps' 11 | 12 | 15 | ... 16 | 17 | 18 | // v0.1 (O) 19 | import { NavermapsProvider } from 'react-naver-maps' 20 | 21 | 24 | 25 | 26 | ``` 27 | 28 | ## Add `` 29 | 30 | 기존 ``컴포넌트가 div의 역할과 navermaps 컴포넌트 역할을 동시에 수행하다보니 `HTMLDivElement`와 `naver.maps.Map` API가 섞여 있었고, 31 | SSR렌더가 되지 않는 등 분리하여 컨트롤하기 어려운 문제가 있었습니다. 32 | 33 | 이를 해결하기 위해 MapDiv 역할을 하는 Container 컴포넌트가 필수적으로 추가되었습니다. 34 | 35 | - `` 컴포넌트에서 id, style, className, HTML event handler를 제거해주세요. 36 | - ``를 감싸는 ``를 추가하고 위에서 제거한 요소를 추가해주세요. 37 | 38 | ``` jsx 39 | // v0.0 (X) 40 | import { NaverMap } from 'react-naver-maps' 41 | 42 | {}} // navermaps event? or html div event? 46 | /> 47 | 48 | // v0.1 (O) 49 | import { Container as MapDiv, NaverMap } from 'react-naver-maps' 50 | 51 | // SSR Ready 52 | {}} // html div event 56 | > 57 | {}} // navermaps event 59 | /> 60 | 61 | ``` 62 | 63 | 64 | ## Suspensed `useNavermaps()` 65 | 66 | `window.naver.maps` 를 사용하고자 할 경우 `useNavermaps()`를 사용해주세요. 67 | React Naver Maps 는 내부적으로 Suspense 를 통해 navermaps client 를 가져오고 있습니다. 68 | 69 | ``는 Suspense를 내장하고 있으므로 ``내부에서 호출할 경우에는 ``를 사용하지 않아도 됩니다. 70 | 71 | ``` jsx 72 | // v0.1 (O) 73 | import { Container as MapDiv, useNavermaps } from 'react-naver-maps' 74 | 75 | <> 76 | 77 | 78 | 79 | // Suspense가 내장되어있습니다 80 | 81 | 82 | 83 | 84 | function MyComponent() { 85 | const navermaps = useNavermaps(); // Suspensed 86 | 87 | return null; 88 | } 89 | ``` 90 | 91 | ## Typescript 지원 92 | 93 | 타입스크립트가 내장되어 지원됩니다. 94 | 95 | 96 | ## 변경사항 종합 97 | 98 | ``` jsx 99 | // v0.0 (X) 100 | import { RenderAfterNavermapsLoaded, NaverMap, useNavermaps } from 'react-naver-maps' 101 | 102 | function App() { 103 | return 104 | } 105 | 106 | function MyMap() { 107 | ... 108 | return ( 109 | 110 | <> 111 | 112 | {}} 116 | /> 117 | 118 | 119 | ) 120 | } 121 | 122 | function Call() { 123 | const navermaps = useNavermaps() 124 | console.log(navermaps) 125 | return null; 126 | } 127 | 128 | ``` 129 | 130 | ``` jsx 131 | // v0.1 (O) 132 | import { Suspense } from 'react' 133 | import { Container as MapDiv, NaverMap, useNavermaps, NavermapsProvider } from 'react-naver-maps' 134 | 135 | // Your App Root 136 | function App() { 137 | return ( 138 | 139 | 140 | 141 | ) 142 | } 143 | 144 | function MyMap() { 145 | ... 146 | return ( 147 | <> 148 | 149 | 150 | 151 | {}} 155 | > 156 | {}} 158 | /> 159 | 160 | 161 | ) 162 | } 163 | 164 | function Call() { 165 | const navermaps = useNavermaps() 166 | console.log(navermaps) 167 | return null; 168 | } 169 | 170 | ``` -------------------------------------------------------------------------------- /website/src/pages/guides/quickstart.mdx: -------------------------------------------------------------------------------- 1 | import { Playground } from 'next-docz' 2 | import { Suspense } from 'react' 3 | import { Container as MapDiv, NaverMap, Marker, useNavermaps } from 'react-naver-maps' 4 | 5 | # Quickstart 6 | 7 | Application 루트에 NaverMaps Provider를 제공해야합니다. 사용하시는 프레임워크에 맞춰 `` 를 감싸주세요. 8 | 9 | ``` jsx 10 | import { NavermapsProvider } from 'react-naver-maps'; 11 | 12 | function App() { 13 | return ( 14 | 18 | 19 | 20 | ) 21 | } 22 | ``` 23 | 24 | 30 | {() => { 31 | function MyMap() { 32 | // instead of window.naver.maps 33 | const navermaps = useNavermaps(); 34 | 35 | return ( 36 | 40 | 43 | 44 | ); 45 | } 46 | 47 | return ( 48 | 52 | 53 | 54 | ); 55 | }} 56 | 57 | -------------------------------------------------------------------------------- /website/src/pages/guides/suspensed-use-navermaps.mdx: -------------------------------------------------------------------------------- 1 | # Suspensed `useNavermaps()` 2 | 3 | `window.naver.maps`를 사용하고자 할 경우 `useNavermaps()`를 사용해주세요. 4 | React Naver Maps 는 내부적으로 Suspense 를 통해 navermaps client 를 가져오고 있습니다. 5 | 6 | ``는 Suspense를 내장하고 있으므로 ``내부에서 호출할 경우에는 ``를 사용하지 않아도 됩니다. 7 | 8 | ``` jsx 9 | import { Container as MapDiv, useNavermaps } from 'react-naver-maps' 10 | 11 | <> 12 | 13 | 14 | 15 | // Suspense가 내장되어있습니다 16 | 17 | 18 | 19 | ``` -------------------------------------------------------------------------------- /website/src/pages/guides/without-component.mdx: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /website/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next'; 2 | 3 | import Intro from './guides/introduction.mdx'; 4 | 5 | const Home: NextPage = () => { 6 | return ( 7 | 8 | ); 9 | }; 10 | 11 | export default Home; 12 | -------------------------------------------------------------------------------- /website/src/samples/accident-death.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // https://navermaps.github.io/maps.js.ncp/docs/data/accidentdeath.js 3 | var accidentDeath={searchResult:{accidentDeath:[{year:"2014",dt_006:"2014031411",dt_006_lv8:"40",cd_008:"1",cd_007:"6",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1335",cd_003_lv1:"1300",cd_014_lv1:"02",cd_014_lv2:"22",cd_014:"22",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"05",cd_036_1:"17",cd_036_1_lv2:"01",cd_036_2:"03",x_coord:"960824 ",y_coord:"1905922 ",grd_lo:"127.05882791",grd_la:"37.15120216"},{year:"2014",dt_006:"2014031313",dt_006_lv8:"23",cd_008:"1",cd_007:"5",no_010:1,injpsn_co:9,no_011:3,no_012:5,no_013:0,cd_003:"1909",cd_003_lv1:"1900",cd_014_lv1:"02",cd_014_lv2:"21",cd_014:"21",cd_027_1_lv1:"01",cd_027_1_lv2:"05",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"03",cd_036_1:"10",cd_036_1_lv2:"02",cd_036_2:"08",x_coord:"1045257 ",y_coord:"1843983 ",grd_lo:"128.01221855",grd_la:"36.58983435"},{year:"2014",dt_006:"2014031121",dt_006_lv8:"10",cd_008:"2",cd_007:"3",no_010:1,injpsn_co:3,no_011:0,no_012:2,no_013:0,cd_003:"2701",cd_003_lv1:"2700",cd_014_lv1:"01",cd_014_lv2:"15",cd_014:"05",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"02",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"974095 ",y_coord:"1828345 ",grd_lo:"127.21091471",grd_la:"36.45233748"},{year:"2014",dt_006:"2014031205",dt_006_lv8:"37",cd_008:"2",cd_007:"4",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1405",cd_003_lv1:"1400",cd_014_lv1:"03",cd_014_lv2:"31",cd_014:"41",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"07",cd_036_1_lv1:"05",cd_036_1:"17",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"1094818 ",y_coord:"2021380 ",grd_lo:"128.58271469",grd_la:"38.18770048"},{year:"2014",dt_006:"2014031209",dt_006_lv8:"50",cd_008:"1",cd_007:"4",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"2202",cd_003_lv1:"2200",cd_014_lv1:"01",cd_014_lv2:"14",cd_014:"04",cd_027_1_lv1:"01",cd_027_1_lv2:"14",cd_043_lv1:"01",cd_043:"01",cd_036_1_lv1:"04",cd_036_1:"16",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"1103199 ",y_coord:"1766794 ",grd_lo:"128.64345545",grd_la:"35.89231981"},{year:"2014",dt_006:"2014031102",dt_006_lv8:"20",cd_008:"2",cd_007:"3",no_010:1,injpsn_co:3,no_011:1,no_012:1,no_013:0,cd_003:"1517",cd_003_lv1:"1500",cd_014_lv1:"03",cd_014_lv2:"34",cd_014:"45",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"1016945 ",y_coord:"1880378 ",grd_lo:"127.70505351",grd_la:"36.94499381"},{year:"2014",dt_006:"2014032319",dt_006_lv8:"25",cd_008:"2",cd_007:"1",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1308",cd_003_lv1:"1300",cd_014_lv1:"01",cd_014_lv2:"15",cd_014:"05",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"07",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"960686 ",y_coord:"1897749 ",grd_lo:"127.05770261",grd_la:"37.07752506"},{year:"2014",dt_006:"2014021718",dt_006_lv8:"15",cd_008:"2",cd_007:"2",no_010:1,injpsn_co:2,no_011:1,no_012:0,no_013:0,cd_003:"1808",cd_003_lv1:"1800",cd_014_lv1:"03",cd_014_lv2:"31",cd_014:"41",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"03",cd_036_1:"09",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"1025531 ",y_coord:"1669535 ",grd_lo:"127.77985607",grd_la:"35.02048014"},{year:"2014",dt_006:"2014021919",dt_006_lv8:"40",cd_008:"2",cd_007:"4",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1902",cd_003_lv1:"1900",cd_014_lv1:"02",cd_014_lv2:"22",cd_014:"22",cd_027_1_lv1:"01",cd_027_1_lv2:"06",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"03",cd_036_2:"12",x_coord:"1169555 ",y_coord:"1780150 ",grd_lo:"129.38125275",grd_la:"36.00342549"},{year:"2014",dt_006:"2014022523",dt_006_lv8:"40",cd_008:"2",cd_007:"3",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1110",cd_003_lv1:"1100",cd_014_lv1:"01",cd_014_lv2:"15",cd_014:"05",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"949594 ",y_coord:"1950269 ",grd_lo:"126.92935115",grd_la:"37.55038341"},{year:"2014",dt_006:"2014022522",dt_006_lv8:"00",cd_008:"2",cd_007:"3",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"2402",cd_003_lv1:"2400",cd_014_lv1:"01",cd_014_lv2:"11",cd_014:"01",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"943638 ",y_coord:"1684345 ",grd_lo:"126.88119723",grd_la:"35.15276899"},{year:"2014",dt_006:"2014022702",dt_006_lv8:"05",cd_008:"2",cd_007:"5",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1324",cd_003_lv1:"1300",cd_014_lv1:"03",cd_014_lv2:"31",cd_014:"41",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"01",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"998124 ",y_coord:"1925390 ",grd_lo:"127.48762685",grd_la:"37.33953297"},{year:"2014",dt_006:"2014022506",dt_006_lv8:"05",cd_008:"1",cd_007:"3",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1902",cd_003_lv1:"1900",cd_014_lv1:"02",cd_014_lv2:"24",cd_014:"25",cd_027_1_lv1:"01",cd_027_1_lv2:"06",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"05",cd_036_1:"17",cd_036_1_lv2:"03",cd_036_2:"12",x_coord:"1168677 ",y_coord:"1784188 ",grd_lo:"129.37237675",grd_la:"36.03996462"},{year:"2014",dt_006:"2014030619",dt_006_lv8:"59",cd_008:"2",cd_007:"5",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1324",cd_003_lv1:"1300",cd_014_lv1:"02",cd_014_lv2:"22",cd_014:"22",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"99",cd_036_2:"99",x_coord:"996280 ",y_coord:"1919670 ",grd_lo:"127.45803888",grd_la:"37.27594171"},{year:"2014",dt_006:"2014030413",dt_006_lv8:"15",cd_008:"1",cd_007:"3",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1501",cd_003_lv1:"1500",cd_014_lv1:"01",cd_014_lv2:"12",cd_014:"02",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"996534 ",y_coord:"1847133 ",grd_lo:"127.46123671",grd_la:"36.62205754"},{year:"2014",dt_006:"2014032110",dt_006_lv8:"55",cd_008:"1",cd_007:"6",no_010:1,injpsn_co:2,no_011:0,no_012:1,no_013:0,cd_003:"2014",cd_003_lv1:"2000",cd_014_lv1:"02",cd_014_lv2:"23",cd_014:"23",cd_027_1_lv1:"01",cd_027_1_lv2:"04",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"06",cd_036_1:"20",cd_036_1_lv2:"05",cd_036_2:"27",x_coord:"1089070 ",y_coord:"1729025 ",grd_lo:"128.48273629",grd_la:"35.55321975"},{year:"2014",dt_006:"2014032305",dt_006_lv8:"42",cd_008:"2",cd_007:"1",no_010:2,injpsn_co:2,no_011:0,no_012:0,no_013:0,cd_003:"1319",cd_003_lv1:"1300",cd_014_lv1:"02",cd_014_lv2:"22",cd_014:"22",cd_027_1_lv1:"01",cd_027_1_lv2:"06",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"03",cd_036_2:"11",x_coord:"977787 ",y_coord:"1932802 ",grd_lo:"127.24904785",grd_la:"37.39405363"},{year:"2014",dt_006:"2014032014",dt_006_lv8:"30",cd_008:"1",cd_007:"5",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"2306",cd_003_lv1:"2300",cd_014_lv1:"01",cd_014_lv2:"15",cd_014:"05",cd_027_1_lv1:"01",cd_027_1_lv2:"19",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"02",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"925924 ",y_coord:"1956027 ",grd_lo:"126.63734058",grd_la:"37.59751713"},{year:"2014",dt_006:"2014032000",dt_006_lv8:"25",cd_008:"2",cd_007:"5",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1124",cd_003_lv1:"1100",cd_014_lv1:"01",cd_014_lv2:"11",cd_014:"01",cd_027_1_lv1:"01",cd_027_1_lv2:"02",cd_043_lv1:"02",cd_043:"07",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"959558 ",y_coord:"1959113 ",grd_lo:"127.0416618",grd_la:"37.63058777"},{year:"2014",dt_006:"2014032008",dt_006_lv8:"30",cd_008:"1",cd_007:"5",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1602",cd_003_lv1:"1600",cd_014_lv1:"01",cd_014_lv2:"15",cd_014:"05",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"04",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"967404 ",y_coord:"1866788 ",grd_lo:"127.134616",grd_la:"36.79868986"},{year:"2014",dt_006:"2014021710",dt_006_lv8:"30",cd_008:"1",cd_007:"2",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1316",cd_003_lv1:"1300",cd_014_lv1:"02",cd_014_lv2:"24",cd_014:"25",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"06",cd_036_1:"20",cd_036_1_lv2:"03",cd_036_2:"11",x_coord:"935683 ",y_coord:"1936996 ",grd_lo:"126.77303474",grd_la:"37.42988837"},{year:"2014",dt_006:"2014021711",dt_006_lv8:"40",cd_008:"1",cd_007:"2",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"2205",cd_003_lv1:"2200",cd_014_lv1:"02",cd_014_lv2:"22",cd_014:"22",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"08",cd_036_1:"22",cd_036_1_lv2:"07",cd_036_2:"21",x_coord:"1099252 ",y_coord:"1769117 ",grd_lo:"128.60001944",grd_la:"35.9136687"},{year:"2014",dt_006:"2014022500",dt_006_lv8:"50",cd_008:"2",cd_007:"3",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"2101",cd_003_lv1:"2100",cd_014_lv1:"01",cd_014_lv2:"11",cd_014:"01",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"912436 ",y_coord:"1502978 ",grd_lo:"126.55714252",grd_la:"33.51511658"},{year:"2014",dt_006:"2014022518",dt_006_lv8:"45",cd_008:"2",cd_007:"3",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1315",cd_003_lv1:"1300",cd_014_lv1:"03",cd_014_lv2:"34",cd_014:"45",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"05",cd_036_1:"27",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"948319 ",y_coord:"1902471 ",grd_lo:"126.91824957",grd_la:"37.11948787"},{year:"2014",dt_006:"2014022420",dt_006_lv8:"00",cd_008:"2",cd_007:"2",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1903",cd_003_lv1:"1900",cd_014_lv1:"03",cd_014_lv2:"34",cd_014:"45",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"05",cd_036_1:"17",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"1158224 ",y_coord:"1779193 ",grd_lo:"129.25539638",grd_la:"35.99670694"},{year:"2014",dt_006:"2014022814",dt_006_lv8:"20",cd_008:"1",cd_007:"6",no_010:1,injpsn_co:3,no_011:2,no_012:0,no_013:0,cd_003:"1706",cd_003_lv1:"1700",cd_014_lv1:"03",cd_014_lv2:"31",cd_014:"41",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"02",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"943546 ",y_coord:"1754962 ",grd_lo:"126.8752818",grd_la:"35.78946116"},{year:"2014",dt_006:"2014031020",dt_006_lv8:"05",cd_008:"2",cd_007:"2",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1501",cd_003_lv1:"1500",cd_014_lv1:"01",cd_014_lv2:"11",cd_014:"01",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"02",cd_036_1:"06",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"998976 ",y_coord:"1850825 ",grd_lo:"127.48854279",grd_la:"36.65534659"},{year:"2014",dt_006:"2014030913",dt_006_lv8:"00",cd_008:"1",cd_007:"1",no_010:1,injpsn_co:3,no_011:0,no_012:2,no_013:0,cd_003:"1403",cd_003_lv1:"1400",cd_014_lv1:"02",cd_014_lv2:"23",cd_014:"23",cd_027_1_lv1:"01",cd_027_1_lv2:"13",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"05",cd_036_1:"17",cd_036_1_lv2:"01",cd_036_2:"02",x_coord:"1141750 ",y_coord:"1948544 ",grd_lo:"129.10417404",grd_la:"37.52531751"},{year:"2014",dt_006:"2014031313",dt_006_lv8:"25",cd_008:"1",cd_007:"5",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1906",cd_003_lv1:"1900",cd_014_lv1:"01",cd_014_lv2:"15",cd_014:"05",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"07",cd_036_1_lv1:"02",cd_036_1:"06",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"1075254 ",y_coord:"1790209 ",grd_lo:"128.33608223",grd_la:"36.10595442"},{year:"2014",dt_006:"2014032122",dt_006_lv8:"50",cd_008:"2",cd_007:"6",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1334",cd_003_lv1:"1300",cd_014_lv1:"03",cd_014_lv2:"31",cd_014:"41",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"05",cd_036_1:"18",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"980954 ",y_coord:"1962002 ",grd_lo:"127.28406961",grd_la:"37.6573202"},{year:"2014",dt_006:"2014032022",dt_006_lv8:"40",cd_008:"2",cd_007:"5",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1116",cd_003_lv1:"1100",cd_014_lv1:"01",cd_014_lv2:"12",cd_014:"02",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"959077 ",y_coord:"1945776 ",grd_lo:"127.03695551",grd_la:"37.51035657"},{year:"2014",dt_006:"2014032020",dt_006_lv8:"12",cd_008:"2",cd_007:"5",no_010:2,injpsn_co:3,no_011:1,no_012:0,no_013:0,cd_003:"1515",cd_003_lv1:"1500",cd_014_lv1:"02",cd_014_lv2:"21",cd_014:"21",cd_027_1_lv1:"01",cd_027_1_lv2:"05",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"03",cd_036_2:"12",x_coord:"994979 ",y_coord:"1888570 ",grd_lo:"127.44109729",grd_la:"36.99899965"},{year:"2014",dt_006:"2014032401",dt_006_lv8:"50",cd_008:"2",cd_007:"2",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1902",cd_003_lv1:"1900",cd_014_lv1:"03",cd_014_lv2:"35",cd_014:"46",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"07",cd_036_1_lv1:"05",cd_036_1:"18",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"1169850 ",y_coord:"1776383 ",grd_lo:"129.34523617",grd_la:"35.9717673"},{year:"2014",dt_006:"2014032215",dt_006_lv8:"05",cd_008:"1",cd_007:"7",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1818",cd_003_lv1:"1800",cd_014_lv1:"03",cd_014_lv2:"34",cd_014:"45",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"09",cd_036_1:"23",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"971775 ",y_coord:"1617925 ",grd_lo:"127.19234731",grd_la:"34.55500878"},{year:"2014",dt_006:"2014032313",dt_006_lv8:"20",cd_008:"1",cd_007:"1",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1923",cd_003_lv1:"1900",cd_014_lv1:"01",cd_014_lv2:"15",cd_014:"05",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"03",cd_036_1:"11",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"1079990 ",y_coord:"1750319 ",grd_lo:"128.38467941",grd_la:"35.74596844"},{year:"2014",dt_006:"2014022421",dt_006_lv8:"47",cd_008:"2",cd_007:"2",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1307",cd_003_lv1:"1300",cd_014_lv1:"03",cd_014_lv2:"31",cd_014:"41",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"07",cd_036_1_lv1:"03",cd_036_1:"10",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"941212 ",y_coord:"1927839 ",grd_lo:"126.83625221",grd_la:"37.34772203"},{year:"2014",dt_006:"2014022615",dt_006_lv8:"04",cd_008:"1",cd_007:"4",no_010:1,injpsn_co:7,no_011:1,no_012:5,no_013:0,cd_003:"1720",cd_003_lv1:"1700",cd_014_lv1:"02",cd_014_lv2:"22",cd_014:"22",cd_027_1_lv1:"01",cd_027_1_lv2:"06",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"01",cd_036_1:"01",cd_036_1_lv2:"01",cd_036_2:"03",x_coord:"932324 ",y_coord:"1749150 ",grd_lo:"126.75159918",grd_la:"35.73635364"},{year:"2014",dt_006:"2014022613",dt_006_lv8:"25",cd_008:"1",cd_007:"4",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1720",cd_003_lv1:"1700",cd_014_lv1:"03",cd_014_lv2:"34",cd_014:"45",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"05",cd_036_1:"18",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"921352 ",y_coord:"1746589 ",grd_lo:"126.62620444",grd_la:"35.71042571"},{year:"2014",dt_006:"2014022617",dt_006_lv8:"00",cd_008:"1",cd_007:"4",no_010:1,injpsn_co:2,no_011:1,no_012:0,no_013:0,cd_003:"1336",cd_003_lv1:"1300",cd_014_lv1:"02",cd_014_lv2:"23",cd_014:"24",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"05",cd_036_1:"17",cd_036_1_lv2:"01",cd_036_2:"04",x_coord:"955518 ",y_coord:"1931773 ",grd_lo:"126.99753157",grd_la:"37.38397722"},{year:"2014",dt_006:"2014022812",dt_006_lv8:"25",cd_008:"1",cd_007:"6",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1602",cd_003_lv1:"1600",cd_014_lv1:"01",cd_014_lv2:"11",cd_014:"01",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"03",cd_036_1:"11",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"967640 ",y_coord:"1867055 ",grd_lo:"127.13725004",grd_la:"36.80110485"},{year:"2014",dt_006:"2014030404",dt_006_lv8:"45",cd_008:"2",cd_007:"3",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1321",cd_003_lv1:"1300",cd_014_lv1:"03",cd_014_lv2:"31",cd_014:"41",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"02",cd_043:"06",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"990184 ",y_coord:"2008157 ",grd_lo:"127.38808533",grd_la:"38.07346448"},{year:"2014",dt_006:"2014030908",dt_006_lv8:"00",cd_008:"1",cd_007:"1",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1303",cd_003_lv1:"1300",cd_014_lv1:"03",cd_014_lv2:"31",cd_014:"41",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"03",cd_036_1_lv1:"03",cd_036_1:"10",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"965734 ",y_coord:"1931973 ",grd_lo:"127.11291942",grd_la:"37.38621395"},{year:"2014",dt_006:"2014030412",dt_006_lv8:"30",cd_008:"1",cd_007:"3",no_010:1,injpsn_co:3,no_011:2,no_012:0,no_013:0,cd_003:"1604",cd_003_lv1:"1600",cd_014_lv1:"03",cd_014_lv2:"31",cd_014:"41",cd_027_1_lv1:"01",cd_027_1_lv2:"02",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"01",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"923293 ",y_coord:"1820676 ",grd_lo:"126.64478935",grd_la:"36.38048771"},{year:"2014",dt_006:"2014031619",dt_006_lv8:"00",cd_008:"2",cd_007:"1",no_010:2,injpsn_co:5,no_011:0,no_012:3,no_013:0,cd_003:"1803",cd_003_lv1:"1800",cd_014_lv1:"02",cd_014_lv2:"21",cd_014:"21",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"03",cd_036_1:"10",cd_036_1_lv2:"02",cd_036_2:"08",x_coord:"1012686 ",y_coord:"1643581 ",grd_lo:"127.63528585",grd_la:"34.79346657"},{year:"2014",dt_006:"2014031506",dt_006_lv8:"03",cd_008:"1",cd_007:"7",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1214",cd_003_lv1:"1200",cd_014_lv1:"01",cd_014_lv2:"11",cd_014:"01",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"01",cd_036_1_lv1:"05",cd_036_1:"17",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"1146622 ",y_coord:"1684355 ",grd_lo:"129.10953528",grd_la:"35.14374806"},{year:"2014",dt_006:"2014031612",dt_006_lv8:"36",cd_008:"1",cd_007:"1",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1819",cd_003_lv1:"1800",cd_014_lv1:"03",cd_014_lv2:"32",cd_014:"42",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"05",cd_036_1:"18",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"957984 ",y_coord:"1640641 ",grd_lo:"127.04090065",grd_la:"34.75938267"},{year:"2014",dt_006:"2014031509",dt_006_lv8:"17",cd_008:"1",cd_007:"7",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1315",cd_003_lv1:"1300",cd_014_lv1:"03",cd_014_lv2:"34",cd_014:"45",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"08",cd_036_1:"22",cd_036_1_lv2:"00",cd_036_2:"00",x_coord:"940451 ",y_coord:"1901457 ",grd_lo:"126.82964478",grd_la:"37.1098887"},{year:"2014",dt_006:"2014031623",dt_006_lv8:"45",cd_008:"2",cd_007:"1",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1723",cd_003_lv1:"1700",cd_014_lv1:"01",cd_014_lv2:"15",cd_014:"05",cd_027_1_lv1:"01",cd_027_1_lv2:"02",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"948275 ",y_coord:"1769033 ",grd_lo:"126.92669777",grd_la:"35.91658175"},{year:"2014",dt_006:"2014031500",dt_006_lv8:"30",cd_008:"2",cd_007:"7",no_010:2,injpsn_co:3,no_011:0,no_012:1,no_013:0,cd_003:"1906",cd_003_lv1:"1900",cd_014_lv1:"01",cd_014_lv2:"15",cd_014:"05",cd_027_1_lv1:"01",cd_027_1_lv2:"02",cd_043_lv1:"02",cd_043:"07",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"1075476 ",y_coord:"1791265 ",grd_lo:"128.33864965",grd_la:"36.11545683"},{year:"2014",dt_006:"2014031513",dt_006_lv8:"10",cd_008:"1",cd_007:"7",no_010:1,injpsn_co:2,no_011:1,no_012:0,no_013:0,cd_003:"1116",cd_003_lv1:"1100",cd_014_lv1:"01",cd_014_lv2:"11",cd_014:"01",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"11",cd_036_2:"25",x_coord:"959667 ",y_coord:"1945850 ",grd_lo:"127.04362711",grd_la:"37.51104954"},{year:"2014",dt_006:"2014031311",dt_006_lv8:"00",cd_008:"1",cd_007:"5",no_010:1,injpsn_co:1,no_011:0,no_012:0,no_013:0,cd_003:"1602",cd_003_lv1:"1600",cd_014_lv1:"02",cd_014_lv2:"24",cd_014:"25",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"07",cd_036_1:"21",cd_036_1_lv2:"03",cd_036_2:"11",x_coord:"969079 ",y_coord:"1866942 ",grd_lo:"127.15338529",grd_la:"36.80013431"},{year:"2014",dt_006:"2014031403",dt_006_lv8:"25",cd_008:"2",cd_007:"6",no_010:1,injpsn_co:3,no_011:0,no_012:2,no_013:0,cd_003:"2404",cd_003_lv1:"2400",cd_014_lv1:"02",cd_014_lv2:"21",cd_014:"21",cd_027_1_lv1:"01",cd_027_1_lv2:"05",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"03",cd_036_1_lv2:"01",cd_036_2:"03",x_coord:"938616 ",y_coord:"1687283 ",grd_lo:"126.82584497",grd_la:"35.17896554"},{year:"2014",dt_006:"2014031218",dt_006_lv8:"45",cd_008:"2",cd_007:"4",no_010:1,injpsn_co:2,no_011:0,no_012:1,no_013:0,cd_003:"1925",cd_003_lv1:"1900",cd_014_lv1:"02",cd_014_lv2:"24",cd_014:"25",cd_027_1_lv1:"01",cd_027_1_lv2:"12",cd_043_lv1:"01",cd_043:"05",cd_036_1_lv1:"01",cd_036_1:"02",cd_036_1_lv2:"01",cd_036_2:"03",x_coord:"1079831 ",y_coord:"1776561 ",grd_lo:"128.38554907",grd_la:"35.98255487"}]},resultCode:"Success"}; 4 | 5 | export { accidentDeath } 6 | /* eslint-enable */ 7 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "jsxImportSource": "@emotion/react" 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | --------------------------------------------------------------------------------