├── .babelrc
├── .editorconfig
├── .github
└── workflows
│ ├── nodejs.yml
│ └── npm-release.yml
├── .gitignore
├── .prettierrc
├── .travis.yml
├── LICENSE
├── MIGRATION.md
├── README.md
├── SECURITY.md
├── __mocks__
├── azure-maps-control.js
└── styleMock.js
├── _config.yml
├── assets
└── coverage.png
├── babel.config.js
├── commitlint.config.js
├── jest.config.js
├── package-lock.json
├── package.json
├── preview
├── react-preview.html
└── react-preview.jsx
├── rollup.config.js
├── src
├── components
│ ├── AzureMap
│ │ ├── AzureMap.test.tsx
│ │ ├── AzureMap.tsx
│ │ ├── __snapshots__
│ │ │ └── AzureMap.test.tsx.snap
│ │ ├── useCreateMapControl.test.tsx
│ │ ├── useCreateMapControls.tsx
│ │ ├── useCreateSprites.test.tsx
│ │ └── useCreateSprites.tsx
│ ├── AzureMapFeature
│ │ ├── AzureMapFeature.test.tsx
│ │ ├── AzureMapFeature.tsx
│ │ ├── useCreateAzureMapFeature.test.tsx
│ │ ├── useCreateAzureMapFeature.ts
│ │ ├── useFeature.test.tsx
│ │ └── useFeature.ts
│ ├── AzureMapMarkers
│ │ └── AzureMapHtmlMarker
│ │ │ ├── AzureMapHtmlMarker.test.tsx
│ │ │ ├── AzureMapHtmlMarker.tsx
│ │ │ └── __snapshots__
│ │ │ └── AzureMapHtmlMarker.test.tsx.snap
│ ├── AzureMapPopup
│ │ ├── AzureMapPopup.test.tsx
│ │ ├── AzureMapPopup.tsx
│ │ ├── useCreateAzureMapPopup.test.tsx
│ │ └── useCreateAzureMapPopup.ts
│ └── helpers
│ │ ├── mapHelper.test.ts
│ │ └── mapHelper.ts
├── contexts
│ ├── AzureMapContext.test.tsx
│ ├── AzureMapContext.tsx
│ ├── AzureMapDataSourceContext.test.tsx
│ ├── AzureMapDataSourceContext.tsx
│ ├── AzureMapLayerContext.test.tsx
│ ├── AzureMapLayerContext.tsx
│ ├── AzureMapVectorTileSourceProvider.test.tsx
│ ├── AzureMapVectorTileSourceProvider.tsx
│ └── __snapshots__
│ │ └── AzureMapLayerContext.test.tsx.snap
├── hooks
│ ├── constructLayer.test.tsx
│ ├── useAzureMapLayer.test.tsx
│ ├── useAzureMapLayer.tsx
│ ├── useCheckRef.test.tsx
│ └── useCheckRef.tsx
├── react-azure-maps.ts
└── types.ts
├── tools
└── semantic-release-prepare.ts
├── tsconfig.json
└── tslint.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/env",
5 | {
6 | "targets": {
7 | "browsers": "last 2 Firefox versions, last 2 Chrome versions, last 2 Edge versions, last 2 Safari versions"
8 | } }
9 | ]
10 | ]
11 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | #root = true
2 |
3 | [*]
4 | indent_style = space
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | max_line_length = 100
10 | indent_size = 2
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches:
9 | - master
10 | pull_request:
11 | branches:
12 | - master
13 |
14 | jobs:
15 | build:
16 | runs-on: ubuntu-latest
17 |
18 | strategy:
19 | matrix:
20 | node-version: [16.x, 18.x, 20.x, 22.x]
21 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
22 |
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Use Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v4
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 | cache: 'npm'
30 | - run: npm ci
31 | - run: npm run test # runs linting and tests
32 | - run: npm run build --if-present
33 | - run: python -m pip install linkcheckmd
34 | - run: python -m linkcheckmd README.md
35 |
--------------------------------------------------------------------------------
/.github/workflows/npm-release.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to NPM when a release is created
2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3 |
4 | name: Publish Node.js Package to NPM
5 |
6 | on:
7 | release:
8 | types: [published]
9 | workflow_dispatch:
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/setup-node@v4
17 | with:
18 | node-version: 20
19 | - run: npm ci --legacy-peer-deps
20 | - run: npm run build --if-present
21 |
22 | publish-npm:
23 | needs: build
24 | runs-on: ubuntu-latest
25 | steps:
26 | - uses: actions/checkout@v4
27 | - uses: actions/setup-node@v4
28 | with:
29 | node-version: 20
30 | registry-url: https://registry.npmjs.org/
31 | - run: npm ci
32 | - run: npm run build --if-present --isNpmBuild
33 | - run: npm publish --access=public --tag=latest
34 | env:
35 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | .nyc_output
4 | .DS_Store
5 | *.log
6 | .vscode
7 | .idea
8 | dist
9 | compiled
10 | .awcache
11 | .rpt2_cache
12 | docs
13 | preview-build
14 | .eslintcache
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "semi": false,
4 | "singleQuote": true
5 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | cache:
3 | directories:
4 | - ~/.npm
5 | notifications:
6 | email: false
7 | node_js:
8 | - '10'
9 | - '11'
10 | - '8'
11 | - '6'
12 | script:
13 | - npm run test:prod && npm run build
14 | after_success:
15 | - npm run travis-deploy-once "npm run report-coverage"
16 | - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm run travis-deploy-once "npm run deploy-docs"; fi
17 | - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm run travis-deploy-once "npm run semantic-release"; fi
18 | branches:
19 | except:
20 | - /^v\d+\.\d+\.\d+$/
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 202020 WiredSolutions
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/MIGRATION.md:
--------------------------------------------------------------------------------
1 | ## Migrating from v0.x to v1.0
2 |
3 | ### azure-maps-control dependency
4 | `azure-maps-control` is installed as a peerDependencies package, you will need to add it to your package.json.
5 | ```
6 | npm install --save azure-maps-control@latest
7 | ```
8 | This will install `azure-maps-control` v3 to your application. You may upgrade it independently in the future. See [AzureMaps WebSDK release notes](https://learn.microsoft.com/azure/azure-maps/release-notes-map-control) for a list of new features and bug fixes.
9 |
10 | ### Styling
11 | v1.0 removes the internal css import from `azure-maps-control` to accommodate usage in Next.js. You will need to add the following stylesheet to your application manually. The stylesheet is required for the marker, popup and control components in `react-azure-maps` to work properly.
12 | ```javascript
13 | import 'azure-maps-control/dist/atlas.min.css'
14 | ```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-Azure-Maps
2 |
3 | This project is community-driven initiative originally created by amazing [@psrednicki](https://github.com/psrednicki), [@msasinowski](https://github.com/msasinowski) and [@tbajda](https://github.com/tbajda) and is now maintained by the Azure Maps team.
4 |
5 | [ ](https://www.npmjs.com/package/react-azure-maps) [](https://github.com/Azure/react-azure-maps/blob/master/LICENSE)
6 |
7 | `React Azure Maps` is a react wrapper for [Azure Maps](https://azure.microsoft.com/pl-pl/services/azure-maps/). The whole library is written in typescript and uses React 16.8+
8 |
9 | ## Installation
10 |
11 | Use the package manager `npm` or `yarn`
12 |
13 | ```bash
14 | npm install react-azure-maps
15 | ```
16 |
17 | or
18 |
19 | ```bash
20 | yarn add react-azure-maps
21 | ```
22 |
23 | ## Styling
24 | Embed the following css to your application. The stylesheet is required for the marker, popup and control components in `react-azure-maps` to work properly.
25 | ```javascript
26 | import 'azure-maps-control/dist/atlas.min.css'
27 | ```
28 |
29 | ## Documentation
30 |
31 | Documentation is available [Documentation](https://react-azure-maps.now.sh)
32 |
33 | Generated documentation from typedoc is available [Documentation](https://azure.github.io/react-azure-maps/)
34 |
35 | ## Compatibility with azure-maps-controls
36 |
37 | ```
38 | 1.0.0 - 3.0.0
39 | 0.2.0 - 2.0.32
40 | 0.1.4 - 2.0.31
41 | 0.1.3 - 2.0.25
42 | ```
43 |
44 | ## Playground
45 |
46 | `React Azure Maps` have a fully documented [Playground Package](https://github.com/Azure/react-azure-maps-playground) that implements a lot of features from [Azure Maps Code Samples](https://samples.azuremaps.com/). If you implement new usage of the map and want to be contributor just create a PR.
47 |
48 | ## Library Implementation Details
49 |
50 | For typescript integration and core functionalities, this library uses the newest version of [Azure Maps Control](https://www.npmjs.com/package/azure-maps-control).
51 | The library is implemented under the hood on `Contexts` and uses all benefits of new react features, like new context API, hooks, etc. Across the whole library, there are three main references that depend on the basic `Azure Maps API`
52 |
53 | `MapReference` which is stored and implemented in
54 |
55 | ```javascript
56 | AzureMapsProvider
57 | ```
58 |
59 | `DataSourceReference` which is stored and implemented in
60 |
61 | ```javascript
62 | AzureMapDataSourceProvider
63 | ```
64 |
65 | `LayerReference` which is stored and implemented in
66 |
67 | ```javascript
68 | AzureMapLayerProvider
69 | ```
70 |
71 | If you want to directly make some changes in the above refs just use one of these contexts and feel free to use it any way you want.
72 | The library implements a lot of ready to use components like `AzureMapFeature, AzureMapHTMLMarker, AzureMapPopup`
73 |
74 | ## Basic Usage
75 |
76 | ```javascript
77 | import React from 'react'
78 | import {AzureMap, AzureMapsProvider, IAzureMapOptions, AuthenticationType} from 'react-azure-maps'
79 |
80 | const option: IAzureMapOptions = {
81 | authOptions: {
82 | authType: AuthenticationType.subscriptionKey,
83 | subscriptionKey: '' // Your subscription key
84 | },
85 | }
86 |
87 | const DefaultMap: React.FC = () => (
88 |
89 |
92 |
93 | );
94 |
95 | export default DefaultMap
96 | ```
97 |
98 | ## Authentication
99 |
100 | The subscription key is intended for development environments only and must not be utilized in a production application. Azure Maps provides various authentication options for applications to use. See [here](https://learn.microsoft.com/en-us/azure/azure-maps/how-to-manage-authentication) for more details.
101 |
102 | ```javascript
103 | // AAD
104 | authOptions: {
105 | authType: AuthenticationType.aad,
106 | clientId: '...',
107 | aadAppId: '...',
108 | aadTenant: '...'
109 | }
110 | ```
111 |
112 | ```javascript
113 | // Anonymous
114 | authOptions: {
115 | authType: AuthenticationType.anonymous,
116 | clientId: '...',
117 | getToken: (resolve, reject) => {
118 | // URL to your authentication service that retrieves an Azure Active Directory Token.
119 | var tokenServiceUrl = "https://example.com/api/GetAzureMapsToken";
120 | fetch(tokenServiceUrl).then(r => r.text()).then(token => resolve(token));
121 | }
122 | }
123 | ```
124 |
125 | ```javascript
126 | // SAS Token
127 | authOptions: {
128 | authType: AuthenticationType.sas,
129 | getToken: (resolve, reject) => {
130 | // URL to your authentication service that retrieves a SAS Token.
131 | var tokenServiceUrl = "https://example.com/api/GetSASToken";
132 | fetch(tokenServiceUrl).then(r => r.text()).then(token => resolve(token));
133 | }
134 | }
135 | ```
136 |
137 | ## Local development with [Playground Package](https://github.com/Azure/react-azure-maps-playground)
138 |
139 | If you want to do some local development using [Playground Package](https://github.com/Azure/react-azure-maps-playground) with local link to the package, you need to make the following steps:
140 |
141 | ```bash
142 | - run yarn watch in `react-azure-maps` package
143 | - run yarn link in `react-azure-maps` package
144 | - go to the `azure-maps-playground` or any other folder or repository and run `yarn link "react-azure-maps"`
145 | ```
146 |
147 | ## Code coverage
148 |
149 | 
150 |
151 | ## Contributing
152 |
153 | Pull requests are welcomed. For major changes, please open an issue first to discuss what you would like to change.
154 |
155 | ## Creators ✨
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |  psrednicki
170 |
171 | 
180 |
181 | |
182 |
183 |  msasinowski
190 |
191 | 
200 |
201 | |
202 |
203 |  tbajda
210 |
211 | 
219 |
220 | |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 | ## License
231 |
232 | [MIT](https://choosealicense.com/licenses/mit/)
233 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/__mocks__/azure-maps-control.js:
--------------------------------------------------------------------------------
1 | class DataSource {
2 | id
3 | options
4 |
5 | constructor(id, options) {
6 | this.id = id
7 | this.options = options
8 | }
9 |
10 | add = jest.fn()
11 | clear = jest.fn()
12 | remove = jest.fn()
13 | importDataFromUrl = jest.fn()
14 | setOptions = jest.fn((options) => (this.options = options))
15 | getId = () => this.id
16 | }
17 |
18 | module.exports = {
19 | Map: jest.fn(() => ({
20 | controls: {
21 | add: jest.fn()
22 | },
23 | events: {
24 | add: jest.fn((_eventName, _targetOrCallback, callback = () => {}) => {
25 | if (typeof _targetOrCallback === 'function') {
26 | _targetOrCallback()
27 | } else {
28 | callback()
29 | }
30 | }),
31 | remove: jest.fn((eventName) => {})
32 | },
33 | imageSprite: {
34 | add: jest.fn(),
35 | createFromTemplate: jest.fn()
36 | },
37 | sources: {
38 | add: jest.fn(),
39 | remove: jest.fn()
40 | },
41 | layers: {
42 | add: jest.fn(),
43 | remove: jest.fn(),
44 | getLayers: jest.fn(() => []),
45 | getLayerById: jest.fn()
46 | },
47 | popups: {
48 | getPopups: jest.fn(() => []),
49 | remove: jest.fn()
50 | },
51 | markers: {
52 | add: jest.fn(),
53 | remove: jest.fn()
54 | },
55 | setTraffic: jest.fn(),
56 | setUserInteraction: jest.fn(),
57 | setCamera: jest.fn(),
58 | setStyle: jest.fn(),
59 | setServiceOptions: jest.fn()
60 | })),
61 |
62 | HtmlMarker: jest.fn((...args) => ({
63 | args,
64 | setOptions: jest.fn(),
65 | getOptions: jest.fn(() => ({
66 | popup: {
67 | isOpen: jest.fn(() => true),
68 | open: jest.fn(() => false),
69 | togglePopup: jest.fn(),
70 | setOptions: jest.fn(),
71 | close: jest.fn()
72 | }
73 | }))
74 | })),
75 | data: {
76 | LineString: jest.fn(() => ({})),
77 | Position: jest.fn(() => ({}))
78 | },
79 | Pixel: jest.fn(() => ({
80 | getHeading: jest.fn(() => 'Heading')
81 | })),
82 | Popup: jest.fn(() => ({
83 | setOptions: jest.fn(),
84 | isOpen: jest.fn(() => false),
85 | open: jest.fn(),
86 | close: jest.fn()
87 | })),
88 | control: {
89 | CompassControl: jest.fn(() => ({ compassOption: 'option' })),
90 | PitchControl: jest.fn(() => ({ pitchOption: 'option' })),
91 | StyleControl: jest.fn(() => ({ styleOption: 'option' })),
92 | ZoomControl: jest.fn(() => ({ zoomOption: 'option' })),
93 | TrafficControl: jest.fn(() => ({ trafficOption: 'option' })),
94 | TrafficLegendControl: jest.fn(() => ({ trafficLegendOption: 'option' })),
95 | ScaleControl: jest.fn(() => ({ scaleOption: 'option' })),
96 | FullscreenControl: jest.fn(() => ({ fullscreenOption: 'option' }))
97 | },
98 | layer: {
99 | ImageLayer: jest.fn((options, id) => ({ layer: 'ImageLayer', options, id })),
100 | TileLayer: jest.fn((options, id) => ({ layer: 'TileLayer', options, id })),
101 | SymbolLayer: jest.fn((options, id, datasourceRef) => ({
102 | layer: 'SymbolLayer',
103 | options,
104 | id,
105 | datasourceRef,
106 | setOptions: jest.fn(),
107 | getId: jest.fn(() => id)
108 | })),
109 | HeatMapLayer: jest.fn((options, id, datasourceRef) => ({
110 | layer: 'HeatLayer',
111 | options,
112 | id,
113 | datasourceRef
114 | })),
115 | LineLayer: jest.fn((options, id, datasourceRef) => ({
116 | layer: 'LineLayer',
117 | options,
118 | id,
119 | datasourceRef
120 | })),
121 | PolygonExtrusionLayer: jest.fn((options, id, datasourceRef) => ({
122 | layer: 'PolygonExtrusionLayer',
123 | options,
124 | id,
125 | datasourceRef
126 | })),
127 | PolygonLayer: jest.fn((options, id, datasourceRef) => ({
128 | layer: 'PolygonLayer',
129 | options,
130 | id,
131 | datasourceRef
132 | })),
133 | BubbleLayer: jest.fn((options, id, datasourceRef) => ({
134 | layer: 'BubbleLayer',
135 | options,
136 | id,
137 | datasourceRef
138 | }))
139 | },
140 | source: {
141 | DataSource,
142 | VectorTileSource: jest.fn((id, options) => ({
143 | getId: jest.fn(() => id),
144 | getOptions: jest.fn(() => options)
145 | }))
146 | },
147 | Shape: jest.fn(() => ({
148 | setCoordinates: jest.fn(),
149 | setProperties: jest.fn()
150 | })),
151 | data: {
152 | Position: jest.fn((...args) => args),
153 | BoundingBox: jest.fn((...args) => args),
154 | Point: jest.fn((coords) => ({ coords, type: 'Point' })),
155 | MultiPoint: jest.fn((coords, bbox) => ({ coords, bbox, type: 'MultiPoint' })),
156 | LineString: jest.fn((coords, bbox) => ({ coords, bbox, type: 'LineString' })),
157 | MultiLineString: jest.fn((multipleCoordinates, bbox) => ({
158 | multipleCoordinates,
159 | bbox,
160 | type: 'MultiLineString'
161 | })),
162 | Polygon: jest.fn((coords, bbox) => ({ coords, bbox, type: 'Polygon' })),
163 | MultiPolygon: jest.fn((multipleDimensionCoordinates, bbox) => ({
164 | multipleDimensionCoordinates,
165 | bbox,
166 | type: 'MultiPolygon'
167 | })),
168 | Feature: jest.fn()
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {}
2 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/assets/coverage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/react-azure-maps/59825cbc6be09144e8685d6c1cf2a2cfebde5144/assets/coverage.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | // Use config in .browserlistrc to decide the target env
5 | require('@babel/preset-env'),
6 | {
7 | // Preserve ES modules. Leave module handling to Rollup.
8 | modules: false,
9 | },
10 | ],
11 | require('@babel/preset-react'),
12 | require('@babel/preset-typescript'),
13 | ],
14 | plugins: [
15 | // Target project using this preset will need @babel/runtime as a run-time dependency
16 | require('@babel/plugin-transform-runtime'),
17 |
18 | // Stage 3 Proposals
19 | // Public and private instance fields : https://github.com/tc39/proposal-class-fields
20 | // Static class features : https://github.com/tc39/proposal-static-class-features
21 | [require('@babel/plugin-proposal-class-properties'), { loose: true }],
22 |
23 | // Finished Proposal - Published in ES 2020
24 | // Optional Chaining : https://github.com/tc39/proposal-optional-chaining
25 | require('@babel/plugin-proposal-optional-chaining'),
26 |
27 | // Finished Proposal - Published in ES 2020
28 | // https://github.com/tc39-transfer/proposal-nullish-coalescing
29 | require('@babel/plugin-proposal-nullish-coalescing-operator'),
30 | ],
31 | }
32 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ['@commitlint/config-conventional'] }
2 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | '.(ts|tsx)': 'ts-jest'
4 | },
5 | globals: {
6 | window: {}
7 | },
8 | testEnvironment: 'node',
9 | testRegex: '(/__tests__/.*|\\.(test|spec))\\.(tsx?|ts?)$',
10 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
11 | moduleNameMapper: {
12 | '\\.(css|scss)$': '/__mocks__/styleMock.js'
13 | },
14 | coveragePathIgnorePatterns: ['/node_modules/', '/test/', `/src/my-example-lib.ts`],
15 | // coverageThreshold: {
16 | // global: {
17 | // branches: 90,
18 | // functions: 95,
19 | // lines: 95,
20 | // statements: 95
21 | // }
22 | // },
23 | collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}']
24 | }
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-azure-maps",
3 | "version": "1.0.3",
4 | "description": "React Wrapper for Azure Maps",
5 | "keywords": [
6 | "react",
7 | "reactjs",
8 | "typescript",
9 | "azure",
10 | "azure-maps",
11 | "azure-maps-control",
12 | "map",
13 | "maps",
14 | "react-azure-maps"
15 | ],
16 | "module": "dist/react-azure-maps.es5.js",
17 | "types": "dist/types/react-azure-maps.d.ts",
18 | "exports": {
19 | ".": {
20 | "import": "./dist/react-azure-maps.es5.js",
21 | "types": "./dist/types/react-azure-maps.d.ts"
22 | }
23 | },
24 | "files": [
25 | "dist"
26 | ],
27 | "author": "WiredSolutions and Microsoft",
28 | "repository": {
29 | "type": "git",
30 | "url": "https://github.com/Azure/react-azure-maps"
31 | },
32 | "license": "MIT",
33 | "engines": {
34 | "node": ">=8.0.0"
35 | },
36 | "scripts": {
37 | "lint": "eslint --max-warnings 0 \"./src/**/*.{js,jsx,mjs,ts,tsx}\"",
38 | "format": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx,css,scss,sass,mdx}\"",
39 | "type-check": "tsc --noEmit",
40 | "commitlint": "commitlint",
41 | "commitmsg": "commitlint -e $GIT_PARAMS",
42 | "build:clean": "rimraf dist",
43 | "build:code": "cross-env NODE_ENV=production rollup --config",
44 | "build:types": "tsc --emitDeclarationOnly",
45 | "build": "npm run build:types && npm run build:code",
46 | "test": "npx jest --coverage --env=jsdom",
47 | "test:watch": "jest --coverage --watch --env=jsdom",
48 | "test:prod": "npm run lint && npm run test -- --no-cache",
49 | "dev:code": "cross-env NODE_ENV=development rollup --config --watch",
50 | "preview:react": "npx parcel preview/react-preview.html --out-dir preview-build --open --no-cache",
51 | "dev": "concurrently 'npm:dev:code' 'npm:preview:react'"
52 | },
53 | "husky": {
54 | "hooks": {
55 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
56 | "pre-commit": "lint-staged"
57 | }
58 | },
59 | "lint-staged": {
60 | "*.{js,jsx,mjs,ts,tsx}": [
61 | "eslint --cache --fix"
62 | ],
63 | "*.{js,jsx,json,ts,tsx,css,scss,sass,mdx}": [
64 | "prettier --write"
65 | ]
66 | },
67 | "eslintConfig": {
68 | "extends": [
69 | "react-app",
70 | "react-app/jest"
71 | ]
72 | },
73 | "devDependencies": {
74 | "@babel/core": "^7.13.10",
75 | "@babel/plugin-proposal-class-properties": "^7.10.4",
76 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
77 | "@babel/plugin-proposal-optional-chaining": "^7.11.0",
78 | "@babel/plugin-transform-runtime": "^7.11.5",
79 | "@babel/preset-env": "^7.11.5",
80 | "@babel/preset-react": "^7.10.4",
81 | "@babel/preset-typescript": "^7.10.4",
82 | "@commitlint/cli": "^11.0.0",
83 | "@commitlint/config-angular": "^8.2.0",
84 | "@commitlint/config-conventional": "^11.0.0",
85 | "@rollup/plugin-babel": "^5.2.1",
86 | "@rollup/plugin-commonjs": "^16.0.0",
87 | "@rollup/plugin-html": "^0.2.0",
88 | "@rollup/plugin-json": "^4.1.0",
89 | "@rollup/plugin-node-resolve": "^10.0.0",
90 | "@rollup/plugin-replace": "^2.3.4",
91 | "@testing-library/jest-dom": "^5.11.4",
92 | "@testing-library/react": "^16.2.0",
93 | "@testing-library/user-event": "^12.1.10",
94 | "@types/jest": "^26.0.15",
95 | "@types/react": "19.0.0",
96 | "@types/react-dom": "19.0.0",
97 | "azure-maps-control": "^3.5.0",
98 | "babel-preset-env": "^1.7.0",
99 | "concurrently": "^5.3.0",
100 | "cross-env": "^7.0.2",
101 | "deasync": "^0.1.30",
102 | "eslint": "^7.14.0",
103 | "husky": "^4.3.0",
104 | "lint-staged": "^10.5.2",
105 | "parcel-bundler": "1.12.3",
106 | "prettier": "^2.2.0",
107 | "react": "^19.0.0",
108 | "react-dom": "^19.0.0",
109 | "react-scripts": "^5.0.1",
110 | "rollup": "^2.33.3",
111 | "rollup-plugin-copy": "^3.4.0",
112 | "rollup-plugin-livereload": "^2.0.0",
113 | "rollup-plugin-node-externals": "^2.2.0",
114 | "rollup-plugin-peer-deps-external": "^2.2.4",
115 | "rollup-plugin-postcss": "^3.1.8",
116 | "rollup-plugin-serve": "^1.1.0",
117 | "rollup-plugin-size-snapshot": "^0.12.0",
118 | "rollup-plugin-terser": "^7.0.2",
119 | "ts-jest": "^26.4.4",
120 | "typescript": "^4.1.2"
121 | },
122 | "peerDependencies": {
123 | "azure-maps-control": "^3.5.0",
124 | "react": "^17.0.2 || ^18.0.0 || ^19.0.0",
125 | "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
126 | },
127 | "dependencies": {
128 | "guid-typescript": "^1.0.9"
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/preview/react-preview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/preview/react-preview.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { AzureMap, AzureMapsProvider, AuthenticationType } from '../dist/react-azure-maps.es5'
4 | import 'azure-maps-control/dist/atlas.min.css'
5 |
6 | const option = {
7 | authOptions: {
8 | authType: AuthenticationType.subscriptionKey,
9 | subscriptionKey: ''
10 | }
11 | }
12 |
13 | const DefaultMap = () => (
14 |
15 |
18 |
19 | )
20 |
21 | export default DefaultMap
22 |
23 | createRoot(document.getElementById('root')).render()
24 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel'
2 | import commonjs from '@rollup/plugin-commonjs'
3 | import json from '@rollup/plugin-json'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import replace from '@rollup/plugin-replace'
6 | import externals from 'rollup-plugin-node-externals'
7 | import pkg from './package.json'
8 | import postcss from 'rollup-plugin-postcss'
9 | import { terser } from 'rollup-plugin-terser'
10 |
11 | const ENV_PRODUCTION = 'production'
12 | const ENV_DEVELOPMENT = 'development'
13 | const env = process.env.NODE_ENV || ENV_PRODUCTION
14 |
15 | if (env !== ENV_DEVELOPMENT && env !== ENV_PRODUCTION) {
16 | console.error(`
17 | Unsupported NODE_ENV: ${env}
18 | Should be either "${ENV_DEVELOPMENT}" or "${ENV_PRODUCTION}"
19 | `)
20 | process.exit(1)
21 | }
22 | const extensions = ['.js', '.jsx', '.ts', '.tsx']
23 |
24 | export default {
25 | input: `src/${pkg.name}.ts`,
26 | output: [
27 | {
28 | file: pkg.module,
29 | format: 'es',
30 | exports: 'named',
31 | preserveModulesRoot: 'src'
32 | }
33 | ].filter(Boolean),
34 | watch: {
35 | include: 'src/**'
36 | },
37 | plugins: [
38 | externals({ peerDeps: true, deps: true }),
39 | replace({
40 | 'process.env.NODE_ENV': JSON.stringify(env),
41 | preventAssignment: true
42 | }),
43 | json(),
44 | postcss({
45 | extensions: ['.css']
46 | }),
47 | resolve({
48 | browser: true,
49 | extensions
50 | }),
51 | commonjs(),
52 | babel({
53 | rootMode: 'upward',
54 | extensions,
55 | babelHelpers: 'runtime',
56 | include: ['./src/**/*']
57 | }),
58 | terser()
59 | ]
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/AzureMap/AzureMap.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, act } from '@testing-library/react'
3 | import { AzureMapsContext } from '../../contexts/AzureMapContext'
4 | import AzureMap from './AzureMap'
5 | import { Map } from 'azure-maps-control'
6 | import { IAzureMap, IAzureMapsContextProps } from '../../types'
7 | import { createImageSprites } from './useCreateSprites'
8 | import { createMapCustomControls, createMapControls } from './useCreateMapControls'
9 |
10 | const LoaderComponent = () => Loader
11 |
12 | jest.mock('./useCreateMapControls', () => {
13 | return {
14 | createMapCustomControls: jest.fn(),
15 | createMapControls: jest.fn()
16 | }
17 | })
18 |
19 | jest.mock('./useCreateSprites')
20 | jest.mock('guid-typescript', () => {
21 | return {
22 | Guid: {
23 | create: jest.fn(() => 'fake_generated_id')
24 | }
25 | }
26 | })
27 |
28 | const mapContextProps = {
29 | mapRef: null,
30 | isMapReady: false,
31 | setMapReady: jest.fn(),
32 | removeMapRef: jest.fn(),
33 | setMapRef: jest.fn()
34 | }
35 |
36 | const wrapWithAzureMapContext = (mapContextProps: IAzureMapsContextProps, mapProps: IAzureMap) => {
37 | return (
38 |
43 |
44 |
45 | )
46 | }
47 |
48 | describe('AzureMap Component', () => {
49 | beforeEach(() => {
50 | mapContextProps.removeMapRef.mockClear()
51 | mapContextProps.setMapReady.mockClear()
52 | mapContextProps.setMapRef.mockClear()
53 | })
54 |
55 | it('should setMapRef on mount', () => {
56 | act(() => {
57 | render(wrapWithAzureMapContext(mapContextProps, {}))
58 | })
59 | expect(mapContextProps.setMapRef).toHaveBeenCalled()
60 | })
61 |
62 | it('should change trafficOptions call setTraffic from mapRef', () => {
63 | const mapRef = new Map('fake', {})
64 | act(() => {
65 | const { rerender } = render(wrapWithAzureMapContext({ ...mapContextProps, mapRef }, {}))
66 | rerender(
67 | wrapWithAzureMapContext(
68 | { ...mapContextProps, mapRef },
69 | { trafficOptions: { some: 'some2' } }
70 | )
71 | )
72 | })
73 | expect(mapRef.setTraffic).toHaveBeenCalledWith({ some: 'some2' })
74 | })
75 |
76 | it('should change userInteraction call setUserInteraction from mapRef', () => {
77 | const mapRef = new Map('fake', {})
78 | act(() => {
79 | const { rerender } = render(wrapWithAzureMapContext({ ...mapContextProps, mapRef }, {}))
80 | rerender(
81 | wrapWithAzureMapContext(
82 | { ...mapContextProps, mapRef },
83 | { userInteraction: { some: 'some2' } }
84 | )
85 | )
86 | })
87 | expect(mapRef.setUserInteraction).toHaveBeenCalledWith({ some: 'some2' })
88 | })
89 |
90 | it('should change cameraOptions call setCamera from mapRef', () => {
91 | const mapRef = new Map('fake', {})
92 | act(() => {
93 | const { rerender } = render(wrapWithAzureMapContext({ ...mapContextProps, mapRef }, {}))
94 | rerender(
95 | wrapWithAzureMapContext(
96 | { ...mapContextProps, mapRef },
97 | { cameraOptions: { some: 'some2' } }
98 | )
99 | )
100 | })
101 | expect(mapRef.setCamera).toHaveBeenCalledWith({ some: 'some2' })
102 | })
103 |
104 | it('should call removeMapRef on unmount of component', () => {
105 | const mapRef = new Map('fake', {})
106 | const { unmount } = render(wrapWithAzureMapContext({ ...mapContextProps, mapRef }, {}))
107 | unmount()
108 | expect(mapContextProps.removeMapRef).toHaveBeenCalled()
109 | })
110 |
111 | it('should call createImageSprites if imageSprites is not falsy', () => {
112 | const mapRef = new Map('fake', {})
113 | render(
114 | wrapWithAzureMapContext(
115 | { ...mapContextProps, mapRef },
116 | { imageSprites: [{ id: 'some_fake_id' }] }
117 | )
118 | )
119 | expect(createImageSprites).toHaveBeenCalled()
120 | })
121 |
122 | it('should call createMapControls if controls is not falsy', () => {
123 | const mapRef = new Map('fake', {})
124 | const fakeControls = [{ controlName: 'fake_control_name' }]
125 | render(wrapWithAzureMapContext({ ...mapContextProps, mapRef }, { controls: fakeControls }))
126 | expect(createMapControls).toHaveBeenCalledWith(expect.any(Object), fakeControls)
127 | })
128 |
129 | it('should call createMapCustomControls if customControls is not falsy', () => {
130 | const mapRef = new Map('fake', {})
131 | const customControls = [
132 | {
133 | control: { onAdd: jest.fn(), onRemove: jest.fn() },
134 | controlOptions: {}
135 | }
136 | ]
137 | render(
138 | wrapWithAzureMapContext(
139 | { ...mapContextProps, mapRef },
140 | {
141 | customControls
142 | }
143 | )
144 | )
145 | expect(createMapCustomControls).toHaveBeenCalledWith(expect.any(Object), customControls)
146 | })
147 |
148 | it('should setTraffic on initial props', () => {
149 | const mapRef = new Map('fake', {})
150 | render(
151 | wrapWithAzureMapContext({ ...mapContextProps, mapRef }, { trafficOptions: { some: 'some2' } })
152 | )
153 | expect(mapRef.setTraffic).toHaveBeenCalledWith({ some: 'some2' })
154 | })
155 |
156 | it('should userInteraction on initial props', () => {
157 | const mapRef = new Map('fake', {})
158 | render(
159 | wrapWithAzureMapContext(
160 | { ...mapContextProps, mapRef },
161 | { userInteraction: { some: 'some2' } }
162 | )
163 | )
164 | expect(mapRef.setUserInteraction).toHaveBeenCalledWith({ some: 'some2' })
165 | })
166 |
167 | it('should cameraOptions on initial props', () => {
168 | const mapRef = new Map('fake', {})
169 | render(
170 | wrapWithAzureMapContext({ ...mapContextProps, mapRef }, { cameraOptions: { some: 'some2' } })
171 | )
172 | expect(mapRef.setCamera).toHaveBeenCalledWith({ some: 'some2' })
173 | })
174 |
175 | it('should setStyle on initial props', () => {
176 | const mapRef = new Map('fake', {})
177 | render(
178 | wrapWithAzureMapContext({ ...mapContextProps, mapRef }, { styleOptions: { some: 'some2' } })
179 | )
180 | expect(mapRef.setStyle).toHaveBeenCalledWith({ some: 'some2' })
181 | })
182 |
183 | it('should setServiceOptions on initial props', () => {
184 | const mapRef = new Map('fake', {})
185 | render(
186 | wrapWithAzureMapContext({ ...mapContextProps, mapRef }, { serviceOptions: { some: 'some2' } })
187 | )
188 | expect(mapRef.setServiceOptions).toHaveBeenCalledWith({ some: 'some2' })
189 | })
190 |
191 | it('should call setMapready on mount of component', () => {
192 | const mapRef = new Map('fake', {})
193 | render(wrapWithAzureMapContext({ ...mapContextProps, mapRef }, {}))
194 | expect(mapContextProps.setMapReady).toHaveBeenCalledWith(true)
195 | })
196 |
197 | it('should add props events to mapRef', () => {
198 | const mapRef = new Map('fake', { options: {} })
199 | const dataCallback = () => {
200 | console.log('some fake text')
201 | }
202 | render(
203 | wrapWithAzureMapContext(
204 | { ...mapContextProps, mapRef },
205 | {
206 | events: {
207 | data: dataCallback
208 | }
209 | }
210 | )
211 | )
212 | expect(mapRef.events.add).toHaveBeenCalledWith('ready', expect.any(Function))
213 | expect(mapRef.events.add).toHaveBeenCalledWith('data', dataCallback)
214 | })
215 |
216 | it('should render LoaderComponent if isMapReady is false and LoaderComponent exists', async () => {
217 | const mapRef = new Map('fake', { options: {} })
218 | const { findByText } = render(
219 | wrapWithAzureMapContext(
220 | { ...mapContextProps, mapRef },
221 | {
222 | LoaderComponent
223 | }
224 | )
225 | )
226 | const loaderElement = await findByText('Loader')
227 | expect(loaderElement).toMatchSnapshot()
228 | })
229 |
230 | it('should create map with div and automatically generated id when if isMapReady is true and LoaderComponent exists', async () => {
231 | const mapRef = new Map('fake', { options: {} })
232 | const { container } = render(
233 | wrapWithAzureMapContext({ ...mapContextProps, mapRef, isMapReady: true }, {})
234 | )
235 | expect(container).toMatchSnapshot()
236 | })
237 |
238 | it('should render map with div and provvided id when if isMapReady is true and LoaderComponent exists', async () => {
239 | const mapRef = new Map('fake', { options: {} })
240 | const { container } = render(
241 | wrapWithAzureMapContext(
242 | { ...mapContextProps, mapRef, isMapReady: true },
243 | {
244 | LoaderComponent,
245 | providedMapId: 'some_fake_map_id'
246 | }
247 | )
248 | )
249 | expect(container).toMatchSnapshot()
250 | })
251 |
252 | afterAll(() => {
253 | jest.unmock('./useCreateSprites')
254 | jest.unmock('./useCreateMapControls')
255 | jest.unmock('guid-typescript')
256 | })
257 | })
258 |
--------------------------------------------------------------------------------
/src/components/AzureMap/AzureMap.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, useContext, useEffect, useState, useRef } from 'react'
2 | import atlas, { Map } from 'azure-maps-control'
3 | import { IAzureMap, IAzureMapsContextProps, MapType } from '../../types'
4 | import { AzureMapsContext } from '../../contexts/AzureMapContext'
5 | import { Guid } from 'guid-typescript'
6 | import { useCheckRef } from '../../hooks/useCheckRef'
7 | import { createImageSprites } from './useCreateSprites'
8 | import { createMapControls, createMapCustomControls } from './useCreateMapControls'
9 |
10 | const AzureMap = memo(
11 | ({
12 | children, // @TODO We need to cover and type all possible childrens that we can pass to this component as child for. ex. Markers etc
13 | LoaderComponent = () => Loading ...
,
14 | providedMapId,
15 | containerClassName,
16 | styles,
17 | options = {},
18 | imageSprites,
19 | controls,
20 | customControls,
21 | events,
22 | cameraOptions,
23 | trafficOptions,
24 | userInteraction,
25 | styleOptions,
26 | serviceOptions
27 | }: IAzureMap) => {
28 | const {
29 | setMapRef,
30 | removeMapRef,
31 | mapRef,
32 | setMapReady,
33 | isMapReady
34 | } = useContext(AzureMapsContext)
35 | const [mapId] = useState(providedMapId || Guid.create().toString())
36 | const mapRefSource = useRef