├── .eslintrc.json
├── .github
└── workflows
│ ├── deploy.yml
│ └── lint.yml
├── .gitignore
├── .storybook
├── main.ts
└── preview.tsx
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── src
├── components
│ ├── Annotation.tsx
│ ├── AnnotationProps.tsx
│ ├── CalloutContainer.tsx
│ ├── Map.tsx
│ ├── MapProps.tsx
│ ├── Marker.tsx
│ ├── MarkerProps.tsx
│ ├── Polygon.tsx
│ ├── PolygonProps.tsx
│ ├── Polyline.tsx
│ └── PolylineProps.tsx
├── context
│ └── MapContext.ts
├── events.ts
├── index.ts
├── stories
│ ├── Annotation.stories.tsx
│ ├── Map.stories.tsx
│ ├── Marker.stories.tsx
│ ├── Polygon.stories.tsx
│ ├── Polyline.stories.tsx
│ ├── Refs.stories.tsx
│ └── stories.css
└── util
│ ├── forwardMapkitEvent.ts
│ ├── loader.ts
│ └── parameters.ts
├── support.md
├── tsconfig.json
└── vite.config.ts
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "airbnb",
4 | "airbnb-typescript"
5 | ],
6 | "parserOptions": {
7 | "project": "./tsconfig.json"
8 | },
9 | "rules": {
10 | "import/no-extraneous-dependencies": [
11 | "error",
12 | {
13 | "devDependencies": [
14 | "**/*.stories.*",
15 | "**/.storybook/**/*.*"
16 | ],
17 | "peerDependencies": true
18 | }
19 | ],
20 | "react/require-default-props": [
21 | "error",
22 | {
23 | "functions": "defaultArguments"
24 | }
25 | ]
26 | },
27 | "overrides": [
28 | {
29 | "files": "**/*.stories.*",
30 | "rules": {
31 | "react/jsx-props-no-spreading": 0,
32 | "react/function-component-definition": 0
33 | }
34 | },
35 | {
36 | "files": "vite.config.ts",
37 | "rules": {
38 | "import/no-extraneous-dependencies": ["error", {
39 | "devDependencies": false
40 | }]
41 | }
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy docs
2 | on:
3 | push:
4 | branches: ['main']
5 | paths: ['src/**', '.storybook/**']
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build-and-deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3
13 | - uses: actions/setup-node@v3
14 | with:
15 | node-version: 22
16 | cache: 'npm'
17 | - name: Install NPM dependencies
18 | run: npm ci
19 | - name: Build Storybook 🔧
20 | run: STORYBOOK_MAPKIT_JS_TOKEN="${{ secrets.MAPKIT_JS_TOKEN }}" npm run build-storybook
21 | shell: bash
22 | - name: Deploy to GitHub Pages 🚀
23 | uses: JamesIves/github-pages-deploy-action@v4
24 | with:
25 | token: ${{ secrets.GITHUB_TOKEN }}
26 | folder: storybook-static
27 | clean: true
28 | clean-exclude: |
29 | .nojekyll
30 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 | on: [push]
3 |
4 | jobs:
5 | lint:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v3
9 | - uses: actions/setup-node@v3
10 | with:
11 | node-version: 22
12 | cache: 'npm'
13 | - name: Install NPM dependencies
14 | run: npm ci
15 | - name: Check EditorConfig
16 | run: npx editorconfig-checker
17 | shell: bash
18 | - name: Check TypeScript
19 | run: npx tsc
20 | shell: bash
21 | - name: Run ESLint
22 | run: npm run lint
23 | shell: bash
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | storybook-static
4 |
--------------------------------------------------------------------------------
/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import type { StorybookConfig } from '@storybook/react-vite';
2 |
3 | const config: StorybookConfig = {
4 | stories: [
5 | '../src/**/*.stories.@(js|jsx|ts|tsx)'
6 | ],
7 |
8 | addons: [
9 | '@storybook/addon-links',
10 | '@storybook/addon-essentials',
11 | '@storybook/addon-interactions',
12 | '@storybook/addon-storysource',
13 | ],
14 |
15 | framework: '@storybook/react-vite',
16 |
17 | docs: {}
18 | };
19 |
20 | export default config;
21 |
--------------------------------------------------------------------------------
/.storybook/preview.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const parameters = {
4 | controls: {
5 | matchers: {
6 | color: /(background|color)$/i,
7 | date: /Date$/,
8 | },
9 | },
10 | };
11 |
12 | export const decorators = [
13 | (Story) => (
14 |
15 |
16 |
17 | ),
18 | ];
19 | export const tags = ['autodocs'];
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Nicolas Ettlin
4 |
5 | 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:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | 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.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mapkit-react
2 | Use Apple Maps in your React apps!
3 |
4 | This library wraps [MapKit JS](https://developer.apple.com/documentation/mapkitjs) in React components. Its declarative, straightforward, and React-idiomatic API allows you to quickly add maps to your project, from prototype to production.
5 |
6 | ## Demo
7 | Check out [**the Storybook examples**](https://nicolapps.github.io/mapkit-react/) to see the available components, experiment with their parameters, and read some code samples.
8 |
9 | You can run the examples locally by cloning the project and running the following command:
10 |
11 | ```sh
12 | STORYBOOK_MAPKIT_JS_TOKEN="…" npm run storybook
13 | ```
14 |
15 | Replace `…` by your MapKit JS token (you can learn how to generate one on the [MapKit JS documentation](https://developer.apple.com/documentation/mapkitjs/creating_and_using_tokens_with_mapkit_js)).
16 |
17 | You can also see the library used in production on [CMUEats](https://cmueats.com/map) and [CMU Map](https://cmumap.com).
18 |
19 | ## Usage
20 | First, add the library to your project like this:
21 |
22 | ```sh
23 | npm install mapkit-react
24 | ```
25 |
26 | You can then use the library in your project like this:
27 |
28 | ```tsx
29 | import React from 'react';
30 | import { Map, Marker } from 'mapkit-react';
31 |
32 | function MyComponent() {
33 | return (
34 |
35 |
36 |
37 | );
38 | }
39 | ```
40 |
41 | You can see all the supported parameters in Storybook (see above).
42 |
43 | ## Features
44 | A complete list of MapKit JS features supported by this library is available on the [Supported MapKit JS features](support.md) page.
45 |
46 | ## Contributing
47 | If you have a question or an idea, you can create an issue. Pull requests are welcome! If you want to contribute, don’t hesitate to look into the unassigned issues.
48 |
49 | If you want to expand the provided API or make breaking changes, please open an issue first to discuss it.
50 |
51 | The project uses [ESLint](https://eslint.org/) and [EditorConfig](https://editorconfig.org/) to ensure code quality and consistency. Checks are run automatically when a commit is pushed to the repository, but it is convenient to install the relevant plugins in your editor to see the errors and warnings in real time.
52 |
53 | ## Contributors
54 |
55 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
56 |
57 |
58 |
59 |
60 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
79 |
80 | ## License
81 | mapkit-react is released under the MIT license, see the [LICENSE](https://github.com/Nicolapps/mapkit-react/blob/main/LICENSE) file for details.
82 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mapkit-react",
3 | "version": "1.16.1",
4 | "description": "A React wrapper for MapKit JS",
5 | "author": "Nicolas Ettlin",
6 | "license": "MIT",
7 | "type": "module",
8 | "homepage": "https://nicolapps.github.io/mapkit-react/",
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/Nicolapps/mapkit-react.git"
12 | },
13 | "bugs": {
14 | "url": "https://github.com/Nicolapps/mapkit-react/issues"
15 | },
16 | "scripts": {
17 | "build": "vite build",
18 | "lint": "npx eslint src --ext .js,.jsx,.ts,.tsx",
19 | "storybook": "storybook dev -p 6006",
20 | "build-storybook": "storybook build",
21 | "prepublishOnly": "npm run build"
22 | },
23 | "main": "dist/mapkit-react.umd.js",
24 | "module": "dist/mapkit-react.js",
25 | "exports": {
26 | ".": {
27 | "types": "./dist/index.d.ts",
28 | "import": "./dist/mapkit-react.js",
29 | "require": "./dist/mapkit-react.umd.js"
30 | }
31 | },
32 | "types": "dist/index.d.ts",
33 | "files": [
34 | "dist"
35 | ],
36 | "dependencies": {
37 | "usehooks-ts": "^3.1.1"
38 | },
39 | "peerDependencies": {
40 | "react": "^18 || ^19",
41 | "react-dom": "^18 || ^19",
42 | "@types/react": "^18 || ^19",
43 | "@types/react-dom": "^18 || ^19"
44 | },
45 | "devDependencies": {
46 | "@babel/core": "^7.20.5",
47 | "@storybook/addon-actions": "^8.6.3",
48 | "@storybook/addon-essentials": "^8.6.3",
49 | "@storybook/addon-interactions": "^8.6.3",
50 | "@storybook/addon-links": "^8.6.3",
51 | "@storybook/addon-storysource": "^8.6.3",
52 | "@storybook/react": "^8.6.3",
53 | "@storybook/react-vite": "^8.6.3",
54 | "@storybook/test": "^8.6.3",
55 | "@types/apple-mapkit-js-browser": "^5.65.3",
56 | "@types/react": "^18.0.25",
57 | "@types/react-dom": "^18.0.10",
58 | "@typescript-eslint/eslint-plugin": "^5.44.0",
59 | "@typescript-eslint/parser": "^5.44.0",
60 | "@vitejs/plugin-react": "^4.3.4",
61 | "babel-loader": "^8.3.0",
62 | "eslint": "^8.28.0",
63 | "eslint-config-airbnb": "^19.0.4",
64 | "eslint-config-airbnb-typescript": "^17.0.0",
65 | "eslint-plugin-import": "^2.26.0",
66 | "eslint-plugin-jsx-a11y": "^6.6.1",
67 | "eslint-plugin-react": "^7.31.11",
68 | "eslint-plugin-react-hooks": "^4.6.0",
69 | "eslint-plugin-storybook": "^0.11.3",
70 | "react": "^18.2.0",
71 | "react-dom": "^18.2.0",
72 | "storybook": "^8.6.3",
73 | "tslib": "^2.4.1",
74 | "typescript": "^4.9.3",
75 | "vite": "^6.2.0",
76 | "vite-plugin-dts": "^1.7.1"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/Annotation.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | useContext,
3 | useEffect,
4 | useState,
5 | useMemo,
6 | useRef,
7 | useLayoutEffect,
8 | } from 'react';
9 | import { createPortal } from 'react-dom';
10 | import MapContext from '../context/MapContext';
11 | import AnnotationProps from './AnnotationProps';
12 | import forwardMapkitEvent from '../util/forwardMapkitEvent';
13 | import CalloutContainer from './CalloutContainer';
14 | import { toMapKitDisplayPriority } from '../util/parameters';
15 |
16 | export default function Annotation({
17 | latitude,
18 | longitude,
19 |
20 | title = '',
21 | subtitle = '',
22 | accessibilityLabel = null,
23 |
24 | size = undefined,
25 |
26 | paddingTop = 0,
27 | paddingRight = 0,
28 | paddingBottom = 0,
29 | paddingLeft = 0,
30 | anchorOffsetX = 0,
31 | anchorOffsetY = 0,
32 |
33 | selected = false,
34 |
35 | onSelect = undefined,
36 | onDeselect = undefined,
37 | onDragStart = undefined,
38 | onDragEnd = undefined,
39 | onDragging = undefined,
40 |
41 | animates = true,
42 | appearanceAnimation = '',
43 | visible = true,
44 |
45 | clusteringIdentifier = null,
46 | displayPriority = undefined,
47 | collisionMode = undefined,
48 |
49 | calloutElement = undefined,
50 | calloutContent = undefined,
51 | calloutLeftAccessory = undefined,
52 | calloutRightAccessory = undefined,
53 |
54 | calloutEnabled = undefined,
55 | calloutOffsetX = 0,
56 | calloutOffsetY = 0,
57 |
58 | draggable = false,
59 | enabled = true,
60 |
61 | children,
62 | }: AnnotationProps) {
63 | const [annotation, setAnnotation] = useState(null);
64 | const contentEl = useMemo(() => document.createElement('div'), []);
65 | const map = useContext(MapContext);
66 |
67 | // Padding
68 | useEffect(() => {
69 | if (!annotation) return;
70 | annotation.padding = new mapkit.Padding(paddingTop, paddingRight, paddingBottom, paddingLeft);
71 | }, [annotation, paddingTop, paddingRight, paddingBottom, paddingLeft]);
72 |
73 | // AnchorOffset
74 | useEffect(() => {
75 | if (!annotation) return;
76 | annotation.anchorOffset = new DOMPoint(anchorOffsetX, anchorOffsetY);
77 | }, [annotation, anchorOffsetX, anchorOffsetY]);
78 |
79 | // CalloutOffset
80 | useEffect(() => {
81 | if (!annotation) return;
82 | annotation.calloutOffset = new DOMPoint(calloutOffsetX, calloutOffsetY);
83 | }, [annotation, calloutOffsetX, calloutOffsetY]);
84 |
85 | const calloutLeftAccessoryRef = useRef(null);
86 | const calloutRightAccessoryRef = useRef(null);
87 | const calloutContentRef = useRef(null);
88 | const calloutElementRef = useRef(null);
89 |
90 | // Callout
91 | useLayoutEffect(() => {
92 | if (!annotation) return;
93 |
94 | const callOutObj: mapkit.AnnotationCalloutDelegate = {};
95 | if (calloutElement && calloutElementRef.current !== null) {
96 | // @ts-expect-error
97 | callOutObj.calloutElementForAnnotation = () => calloutElementRef.current;
98 | }
99 | if (
100 | calloutLeftAccessory
101 | && calloutLeftAccessoryRef.current !== null
102 | ) {
103 | // @ts-expect-error
104 | callOutObj.calloutLeftAccessoryForAnnotation = () => calloutLeftAccessoryRef
105 | .current;
106 | }
107 | if (
108 | calloutRightAccessory
109 | && calloutRightAccessoryRef.current !== null
110 | ) {
111 | // @ts-expect-error
112 | callOutObj.calloutRightAccessoryForAnnotation = () => calloutRightAccessoryRef
113 | .current;
114 | }
115 | if (calloutContent && calloutContentRef.current !== null) {
116 | // @ts-expect-error
117 | callOutObj.calloutContentForAnnotation = () => calloutContentRef.current;
118 | }
119 | if (Object.keys(callOutObj).length > 0) {
120 | annotation.callout = callOutObj;
121 | } else {
122 | // @ts-expect-error
123 | delete annotation.callout;
124 | }
125 |
126 | // eslint-disable-next-line consistent-return
127 | return () => {
128 | // @ts-expect-error
129 | delete annotation.callout;
130 | };
131 | }, [
132 | annotation,
133 | calloutElement,
134 | calloutLeftAccessory,
135 | calloutRightAccessory,
136 | calloutContent,
137 | calloutElementRef.current,
138 | calloutLeftAccessoryRef.current,
139 | calloutRightAccessoryRef.current,
140 | calloutContentRef.current,
141 | ]);
142 |
143 | // Collision Mode
144 | useEffect(() => {
145 | if (!annotation) return;
146 |
147 | if (collisionMode === 'Circle') {
148 | annotation.collisionMode = mapkit.Annotation.CollisionMode.Circle;
149 | } else if (collisionMode === 'Rectangle') {
150 | annotation.collisionMode = mapkit.Annotation.CollisionMode.Rectangle;
151 | } else {
152 | // @ts-ignore
153 | delete annotation.collisionMode;
154 | }
155 | }, [annotation, collisionMode]);
156 |
157 | // Display Priority
158 | useEffect(() => {
159 | if (!annotation) return;
160 | // @ts-ignore
161 | if (displayPriority === undefined) { delete annotation.displayPriority; return; }
162 | // @ts-ignore
163 | annotation.displayPriority = toMapKitDisplayPriority(displayPriority);
164 | }, [annotation, displayPriority]);
165 |
166 | // Simple values properties
167 | const properties = {
168 | title,
169 | subtitle,
170 | accessibilityLabel,
171 |
172 | size,
173 |
174 | selected,
175 | animates,
176 | appearanceAnimation,
177 | draggable,
178 | enabled,
179 | visible,
180 |
181 | clusteringIdentifier,
182 |
183 | calloutEnabled,
184 | };
185 | Object.entries(properties).forEach(([propertyName, prop]) => {
186 | useEffect(() => {
187 | if (!annotation) return;
188 | // @ts-ignore
189 | if (prop === undefined) { delete annotation[propertyName]; return; }
190 | // @ts-ignore
191 | annotation[propertyName] = prop;
192 | }, [annotation, prop]);
193 | });
194 |
195 | // Events
196 | const handlerWithoutParameters = () => { };
197 | const events = [
198 | { name: 'select', handler: onSelect },
199 | { name: 'deselect', handler: onDeselect },
200 | { name: 'drag-start', handler: onDragStart },
201 | ] as const;
202 | events.forEach(({ name, handler }) => {
203 | forwardMapkitEvent(annotation, name, handler, handlerWithoutParameters);
204 | });
205 |
206 | const dragEndParameters = () => ({
207 | latitude: annotation!.coordinate.latitude,
208 | longitude: annotation!.coordinate.longitude,
209 | });
210 | const draggingParameters = (e: { coordinate: mapkit.Coordinate }) => ({
211 | latitude: e.coordinate.latitude,
212 | longitude: e.coordinate.longitude,
213 | });
214 | forwardMapkitEvent(annotation, 'drag-end', onDragEnd, dragEndParameters);
215 | forwardMapkitEvent(annotation, 'dragging', onDragging, draggingParameters);
216 |
217 | // Coordinates - This needs to be the last useEffect,
218 | // as removing the annotation needs to be the last unmount action
219 | useLayoutEffect(() => {
220 | if (map === null) return undefined;
221 |
222 | const a = new mapkit.Annotation(
223 | new mapkit.Coordinate(latitude, longitude),
224 | () => contentEl,
225 | );
226 | map.addAnnotation(a);
227 | setAnnotation(a);
228 |
229 | return () => {
230 | map.removeAnnotation(a);
231 | };
232 | }, [map, latitude, longitude]);
233 |
234 | return (
235 | <>
236 | {createPortal(
237 |
238 | {(calloutContent !== undefined) && (
239 |
243 | {calloutContent}
244 |
245 | )}
246 | {(calloutLeftAccessory !== undefined) && (
247 |
251 | {calloutLeftAccessory}
252 |
253 | )}
254 | {(calloutRightAccessory !== undefined) && (
255 |
259 | {calloutRightAccessory}
260 |
261 | )}
262 | {(calloutElement !== undefined) && (
263 |
267 | {calloutElement}
268 |
269 | )}
270 |
,
271 | document.body,
272 | )}
273 | {createPortal(children, contentEl)}
274 | >
275 | );
276 | }
277 |
--------------------------------------------------------------------------------
/src/components/AnnotationProps.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 | import { Coordinate } from '../util/parameters';
3 |
4 | export default interface AnnotationProps {
5 | /**
6 | * The latitude in degrees.
7 | */
8 | latitude: number;
9 |
10 | /**
11 | * The longitude in degrees.
12 | */
13 | longitude: number;
14 |
15 | /**
16 | * The text to display in the annotation’s callout.
17 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotation/2973835-title}
18 | */
19 | title?: string;
20 |
21 | /**
22 | * The text to display as a subtitle on the second line of an annotation’s callout.
23 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotation/2973834-subtitle}
24 | */
25 | subtitle?: string;
26 |
27 | /**
28 | * Accessibility text for the annotation.
29 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotation/2973814-accessibilitylabel}
30 | */
31 | accessibilityLabel?: string | null;
32 |
33 | /**
34 | * The desired dimensions of the annotation, in CSS pixels.
35 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973833-size}
36 | * @example `{width: 100, height: 100}`
37 | */
38 | size?: { width: number; height: number };
39 |
40 | /**
41 | * The amount of padding, in CSS pixels, to inset the map from the top edge.
42 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
43 | */
44 | paddingTop?: number;
45 |
46 | /**
47 | * The amount of padding, in CSS pixels, to inset the map from the right edge.
48 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
49 | */
50 | paddingRight?: number;
51 |
52 | /**
53 | * The amount of padding, in CSS pixels, to inset the map from the bottom edge.
54 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
55 | */
56 | paddingBottom?: number;
57 |
58 | /**
59 | * The amount of padding, in CSS pixels, to inset the map from the left edge.
60 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
61 | */
62 | paddingLeft?: number;
63 |
64 | /**
65 | * An X offset that changes the annotation’s default anchor point.
66 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973816-anchoroffset}
67 | */
68 | anchorOffsetX?: number;
69 |
70 | /**
71 | * An Y offset that changes the annotation’s default anchor point.
72 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973816-anchoroffset}
73 | */
74 | anchorOffsetY?: number;
75 |
76 | /**
77 | * A Boolean value that determines whether the map displays the annotation in a selected state.
78 | */
79 | selected?: boolean;
80 |
81 | /**
82 | * Event fired when the annotation is selected.
83 | */
84 | onSelect?: () => void;
85 |
86 | /**
87 | * Event fired when the annotation is deselected.
88 | */
89 | onDeselect?: () => void;
90 |
91 | /**
92 | * Event fired when the user starts a drag for the annotation.
93 | */
94 | onDragStart?: () => void;
95 |
96 | /**
97 | * Event fired when the user ends a drag for the annotation.
98 | */
99 | onDragEnd?: (newPosition: Coordinate) => void;
100 |
101 | /**
102 | * Event fired when the user a drags the annotation.
103 | */
104 | onDragging?: (newPosition: Coordinate) => void;
105 |
106 | /**
107 | * A Boolean value that determines whether the map animates the annotation.
108 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973817-animates}
109 | */
110 | animates?: boolean;
111 |
112 | /**
113 | * A CSS animation that runs when the annotation appears on the map.
114 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973818-appearanceanimation}
115 | */
116 | appearanceAnimation?: string;
117 |
118 | /**
119 | * A Boolean value that determines whether the user can drag the annotation.
120 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973826-draggable}
121 | */
122 | draggable?: boolean;
123 |
124 | /**
125 | * A Boolean value that determines whether the annotation responds to user interaction.
126 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973828-enabled}
127 | */
128 | enabled?: boolean;
129 |
130 | /**
131 | * A Boolean value that determines whether the annotation is visible or hidden.
132 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotationconstructoroptions/2991172-visible}
133 | */
134 | visible?: boolean;
135 |
136 | /**
137 | * React children to render inside the annotation.
138 | */
139 | children?: React.ReactNode;
140 |
141 | /**
142 | * A shared identifier for all of the member annotations.
143 | * An annotation needs a clusteringIdentifier to be part of an annotation cluster.
144 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotations/clustering_annotations}
145 | */
146 | clusteringIdentifier?: string | null;
147 |
148 | /**
149 | * A mode that determines the shape of the collision frame.
150 | * Rectangle | Circle | None
151 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973822-collisionmode}
152 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/collisionmode}
153 | */
154 | collisionMode?: 'Rectangle' | 'Circle' | null;
155 |
156 | /**
157 | * A numeric hint that the map uses to prioritize how it displays annotations.
158 | *
159 | * Is either any number from `0` to `1000`,
160 | * or a preset value: `"low"` (250), `"high"` (750), or `"required"` (1000).
161 | *
162 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973825-displaypriority}
163 | */
164 | displayPriority?: number | 'low' | 'high' | 'required';
165 |
166 | /**
167 | * An X offset that changes the annotation callout’s default placement.
168 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973821-calloutoffset}
169 | */
170 | calloutOffsetX?: number;
171 |
172 | /**
173 | * An Y offset that changes the annotation callout’s default placement.
174 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973821-calloutoffset}
175 | */
176 | calloutOffsetY?: number;
177 |
178 | /**
179 | * A Boolean value that determines whether the map shows an annotation’s callout.
180 | * If the title is empty, the framework can’t show the standard callout even if property is true.
181 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973820-calloutenabled}
182 | */
183 | calloutEnabled?: boolean;
184 |
185 | /**
186 | * Returns an element to use as a custom accessory on the left side of the callout content area.
187 | *
188 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotationcalloutdelegate/2991150-calloutleftaccessoryforannotatio}
189 | */
190 | calloutLeftAccessory?: ReactNode;
191 |
192 | /**
193 | * Returns an element to use as a custom accessory on the right side of the callout content area.
194 | *
195 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotationcalloutdelegate/2991151-calloutrightaccessoryforannotati}
196 | */
197 | calloutRightAccessory?: ReactNode;
198 |
199 | /**
200 | * Returns custom content for the callout bubble.
201 | *
202 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotationcalloutdelegate/2991148-calloutcontentforannotation}
203 | */
204 | calloutContent?: ReactNode;
205 |
206 | /**
207 | * Returns an element representing a custom callout.
208 | *
209 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotationcalloutdelegate/2991148-calloutcontentforannotation}
210 | */
211 | calloutElement?: ReactNode;
212 | }
213 |
--------------------------------------------------------------------------------
/src/components/CalloutContainer.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 |
3 | const CalloutContainer = React.forwardRef<
4 | HTMLDivElement,
5 | // https://stackoverflow.com/a/69746922
6 | // eslint-disable-next-line react/require-default-props
7 | React.PropsWithChildren<{ children: ReactNode, type?: string }>
8 | >((
9 | { children, type = 'container' },
10 | ref,
11 | ) => (
12 |
13 | {children}
14 |
15 | ));
16 |
17 | export default CalloutContainer;
18 |
--------------------------------------------------------------------------------
/src/components/Map.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | useState, useEffect, useRef, useImperativeHandle,
3 | } from 'react';
4 | import { useMediaQuery } from 'usehooks-ts';
5 | import MapContext from '../context/MapContext';
6 | import load from '../util/loader';
7 | import {
8 | ColorScheme, Distances, FeatureVisibility, LoadPriority, MapType,
9 | fromMapKitMapType,
10 | toMapKitColorScheme, toMapKitCoordinateRegion, toMapKitDistances,
11 | toMapKitLoadPriority, toMapKitMapType, toMapKitPOICategory,
12 | toMapKitFeatureVisibility, fromMapKitRegion,
13 | } from '../util/parameters';
14 | import MapProps from './MapProps';
15 | import forwardMapkitEvent from '../util/forwardMapkitEvent';
16 |
17 | const Map = React.forwardRef>(({
18 | children = undefined,
19 |
20 | load: customLoad,
21 | token,
22 |
23 | colorScheme = ColorScheme.Light,
24 | mapType = MapType.Standard,
25 | distances = Distances.Adaptive,
26 | loadPriority = LoadPriority.LandCover,
27 |
28 | isRotationEnabled = true,
29 | isScrollEnabled = true,
30 | isZoomEnabled = true,
31 | showsCompass = FeatureVisibility.Adaptive,
32 | showsScale = FeatureVisibility.Hidden,
33 | showsMapTypeControl = true,
34 | showsZoomControl = true,
35 | showsUserLocationControl = false,
36 | showsPointsOfInterest = true,
37 | showsUserLocation = false,
38 | tracksUserLocation = false,
39 | allowWheelToZoom = false,
40 |
41 | includedPOICategories = undefined,
42 | excludedPOICategories = undefined,
43 |
44 | paddingTop = 0,
45 | paddingRight = 0,
46 | paddingBottom = 0,
47 | paddingLeft = 0,
48 |
49 | initialRegion = undefined,
50 | cameraBoundary = undefined,
51 | minCameraDistance = 0,
52 | maxCameraDistance = Infinity,
53 |
54 | onLoad = undefined,
55 |
56 | onRegionChangeStart = undefined,
57 | onRegionChangeEnd = undefined,
58 | onMapTypeChange = undefined,
59 |
60 | onSingleTap = undefined,
61 | onDoubleTap = undefined,
62 | onLongPress = undefined,
63 |
64 | onUserLocationChange = undefined,
65 | onUserLocationError = undefined,
66 |
67 | onClick = undefined,
68 | onMouseMove = undefined,
69 | onMouseDown = undefined,
70 | onMouseUp = undefined,
71 | }, mapRef) => {
72 | const [map, setMap] = useState(null);
73 | const element = useRef(null);
74 | const exists = useRef(false);
75 |
76 | // Load the map
77 | useEffect(() => {
78 | const loadMap = typeof customLoad === 'function' ? customLoad : load;
79 | loadMap(token).then(() => {
80 | if (exists.current) return;
81 | const options = initialRegion
82 | ? { region: toMapKitCoordinateRegion(initialRegion) }
83 | : {};
84 | setMap(new mapkit.Map(element.current!, options));
85 | exists.current = true;
86 | });
87 |
88 | return () => {
89 | if (map) {
90 | map.destroy();
91 | exists.current = false;
92 | }
93 | };
94 | }, []);
95 |
96 | // Fire the `onLoad` event
97 | useEffect(() => {
98 | if (map !== null) {
99 | onLoad?.();
100 | }
101 | }, [map]);
102 |
103 | // Expose the map using a forward ref
104 | useImperativeHandle(mapRef, () => map!, [map]);
105 |
106 | const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
107 | // Enum properties
108 | useEffect(() => {
109 | if (!map) return;
110 | map.colorScheme = toMapKitColorScheme(colorScheme, isDarkMode);
111 | }, [map, colorScheme, isDarkMode]);
112 | useEffect(() => {
113 | if (!map) return;
114 | map.mapType = toMapKitMapType(mapType);
115 | }, [map, mapType]);
116 | useEffect(() => {
117 | if (!map) return;
118 | map.distances = toMapKitDistances(distances);
119 | }, [map, distances]);
120 | useEffect(() => {
121 | if (!map) return;
122 | // @ts-ignore
123 | map.loadPriority = toMapKitLoadPriority(loadPriority);
124 | }, [map, loadPriority]);
125 |
126 | // Simple values properties
127 | const booleanProperties = {
128 | isRotationEnabled,
129 | isScrollEnabled,
130 | isZoomEnabled,
131 | showsMapTypeControl,
132 | showsZoomControl,
133 | showsUserLocationControl,
134 | showsPointsOfInterest,
135 | showsUserLocation,
136 | tracksUserLocation,
137 | };
138 | Object.entries(booleanProperties).forEach(([propertyName, prop]) => {
139 | useEffect(() => {
140 | if (!map) return;
141 | // @ts-ignore
142 | map[propertyName] = prop;
143 | }, [map, prop]);
144 | });
145 |
146 | // Feature visibility properties
147 | const featureVisibilityProperties = {
148 | showsCompass,
149 | showsScale,
150 | };
151 | Object.entries(featureVisibilityProperties).forEach(([propertyName, prop]) => {
152 | useEffect(() => {
153 | if (!map) return;
154 | // @ts-ignore
155 | map[propertyName] = toMapKitFeatureVisibility(prop);
156 | }, [map, prop]);
157 | });
158 |
159 | // Experimental feature
160 | useEffect(() => {
161 | if (!map) return;
162 | try {
163 | // @ts-ignore
164 | // eslint-disable-next-line no-underscore-dangle
165 | map._allowWheelToZoom = allowWheelToZoom;
166 | } catch { /* ignore errors */ }
167 | }, [map, allowWheelToZoom]);
168 |
169 | // Padding
170 | useEffect(() => {
171 | if (!map) return;
172 | map.padding = new mapkit.Padding(paddingTop, paddingRight, paddingBottom, paddingLeft);
173 | }, [map, paddingTop, paddingRight, paddingBottom, paddingLeft]);
174 |
175 | // Camera boundary
176 | useEffect(() => {
177 | if (!map) return;
178 | // @ts-ignore
179 | map.cameraBoundary = cameraBoundary ? toMapKitCoordinateRegion(cameraBoundary) : null;
180 | }, [map, cameraBoundary]);
181 |
182 | // Camera zoom range
183 | useEffect(() => {
184 | if (!map) return;
185 | // @ts-ignore
186 | map.cameraZoomRange = new mapkit.CameraZoomRange(minCameraDistance, maxCameraDistance);
187 | }, [map, minCameraDistance, maxCameraDistance]);
188 |
189 | // Point of interest filter
190 | useEffect(() => {
191 | if (!map) return;
192 |
193 | if (includedPOICategories && excludedPOICategories) {
194 | throw new Error('Can’t specify both includedPOICategories and excludedPOICategories.');
195 | } else if (includedPOICategories) {
196 | map.pointOfInterestFilter = mapkit.PointOfInterestFilter.including(
197 | includedPOICategories.map(toMapKitPOICategory),
198 | );
199 | } else if (excludedPOICategories) {
200 | map.pointOfInterestFilter = mapkit.PointOfInterestFilter.excluding(
201 | excludedPOICategories.map(toMapKitPOICategory),
202 | );
203 | } else {
204 | // @ts-ignore
205 | delete map.pointOfInterestFilter;
206 | }
207 | }, [map, includedPOICategories, excludedPOICategories]);
208 |
209 | // MapKit JS events
210 | const regionHandler = () => fromMapKitRegion(map!.region);
211 | forwardMapkitEvent(map, 'region-change-start', onRegionChangeStart, regionHandler);
212 | forwardMapkitEvent(map, 'region-change-end', onRegionChangeEnd, regionHandler);
213 | forwardMapkitEvent(map, 'map-type-change', onMapTypeChange, () => fromMapKitMapType(map!.mapType));
214 |
215 | type MapKitMapInteractionEvent = {
216 | domEvents: Event[],
217 | pointOnPage: DOMPoint,
218 | };
219 | const interactionEvent = ({ domEvents, pointOnPage }: MapKitMapInteractionEvent) => ({
220 | domEvents,
221 | pointOnPage,
222 | toCoordinates: () => map!.convertPointOnPageToCoordinate(pointOnPage),
223 | });
224 | forwardMapkitEvent(map, 'single-tap', onSingleTap, interactionEvent);
225 | forwardMapkitEvent(map, 'double-tap', onDoubleTap, interactionEvent);
226 | forwardMapkitEvent(map, 'long-press', onLongPress, interactionEvent);
227 |
228 | type MapKitUserLocationChangeEvent = {
229 | coordinate: mapkit.Coordinate,
230 | timestamp: Date,
231 | floorLevel: number | undefined | null,
232 | };
233 | forwardMapkitEvent(map, 'user-location-change', onUserLocationChange, ({ coordinate: { latitude, longitude }, timestamp, floorLevel }: MapKitUserLocationChangeEvent) => ({
234 | coordinate: { latitude, longitude },
235 | timestamp,
236 | floorLevel,
237 | }));
238 | type MapKitUserLocationErrorEvent = {
239 | code: 1 | 2 | 3 | 4,
240 | message: string,
241 | };
242 | forwardMapkitEvent(map, 'user-location-error', onUserLocationError, ({ code, message }: MapKitUserLocationErrorEvent) => ({ code, message }));
243 |
244 | // Native JavaScript events
245 | const domEvents = [
246 | { name: 'click', handler: onClick },
247 | { name: 'mousemove', handler: onMouseMove },
248 | { name: 'mousedown', handler: onMouseDown },
249 | { name: 'mouseup', handler: onMouseUp },
250 | ] as const;
251 | domEvents.forEach(({ name, handler }) => {
252 | useEffect(() => {
253 | if (!map || !handler) return undefined;
254 |
255 | const listener = (e: MouseEvent) => {
256 | handler({
257 | domEvents: [e],
258 | pointOnPage: { x: e.pageX, y: e.pageY },
259 | toCoordinates() {
260 | const { latitude, longitude }: mapkit.Coordinate = map
261 | .convertPointOnPageToCoordinate(new DOMPoint(e.pageX, e.pageY));
262 | return { latitude, longitude };
263 | },
264 | });
265 | };
266 |
267 | element.current?.addEventListener(name, listener);
268 | return () => element.current?.removeEventListener(name, listener);
269 | }, [map, handler]);
270 | });
271 |
272 | return (
273 |
274 |
275 |
276 | {children}
277 |
278 |
279 |
280 | );
281 | });
282 | export default Map;
283 |
--------------------------------------------------------------------------------
/src/components/MapProps.tsx:
--------------------------------------------------------------------------------
1 | import { MapInteractionEvent, UserLocationChangeEvent, UserLocationErrorEvent } from '../events';
2 | import {
3 | ColorScheme, MapType, Distances, LoadPriority, CoordinateRegion,
4 | PointOfInterestCategory,
5 | FeatureVisibility,
6 | } from '../util/parameters';
7 |
8 | export default interface MapProps {
9 | /**
10 | * Custom load method for MapKit JS.
11 | */
12 | load?: (token: string) => Promise;
13 |
14 | /**
15 | * The token provided by MapKit JS.
16 | */
17 | token: string;
18 |
19 | /**
20 | * The map’s color scheme when displaying standard or muted standard map types.
21 | * Offers the options ColorScheme.Dark, ColorScheme.Light and ColorScheme.Auto.
22 | * Auto will select automatically the appropriate MapKit color scheme
23 | * based on the browser preference.
24 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/3122649-colorscheme}
25 | */
26 | colorScheme?: ColorScheme;
27 |
28 | /**
29 | * The type of data that the map displays.
30 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2973919-maptype}
31 | */
32 | mapType?: MapType;
33 |
34 | /**
35 | * The system of measurement that displays on the map.
36 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/3122650-distances}
37 | */
38 | distances?: Distances;
39 |
40 | /**
41 | * A value MapKit JS uses for prioritizing the visibility of specific map
42 | * features before the underlaying map tiles.
43 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/4096373-loadpriority}
44 | */
45 | loadPriority?: LoadPriority;
46 |
47 | /**
48 | * A Boolean value that determines whether the user may rotate the map using
49 | * the compass control or a rotate gesture.
50 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2991321-isrotationenabled}
51 | */
52 | isRotationEnabled?: boolean;
53 |
54 | /**
55 | * A Boolean value that determines whether the user can cause the map to scroll
56 | * with a pointing device or with gestures on a touchscreen.
57 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2978329-isscrollenabled}
58 | */
59 | isScrollEnabled?: boolean;
60 |
61 | /**
62 | * A Boolean value that determines whether the user may zoom in and out on the
63 | * map using pinch gestures or the zoom control.
64 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2978330-iszoomenabled}
65 | */
66 | isZoomEnabled?: boolean;
67 |
68 | /**
69 | * A feature visibility setting that determines when the compass is visible.
70 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2992819-showscompass}
71 | */
72 | showsCompass?: FeatureVisibility;
73 |
74 | /**
75 | * A feature visibility setting that determines when the map displays
76 | * the map’s scale indicator.
77 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2973941-showsscale}
78 | */
79 | showsScale?: FeatureVisibility;
80 |
81 | /**
82 | * A Boolean value that determines whether to display a control that lets
83 | * users choose the map type.
84 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2973939-showsmaptypecontrol}
85 | */
86 | showsMapTypeControl?: boolean;
87 |
88 | /**
89 | * A Boolean value that determines whether to display a control for zooming
90 | * in and zooming out on a map.
91 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2973944-showszoomcontrol}
92 | */
93 | showsZoomControl?: boolean;
94 |
95 | /**
96 | * A Boolean value that determines whether the user location control is visible.
97 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2973943-showsuserlocationcontrol}
98 | */
99 | showsUserLocationControl?: boolean;
100 |
101 | /**
102 | * A Boolean value that determines whether the map displays points of interest.
103 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2973940-showspointsofinterest}
104 | */
105 | showsPointsOfInterest?: boolean;
106 |
107 | /**
108 | * A Boolean value that determines whether to show the user's location on
109 | * the map.
110 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2973942-showsuserlocation}
111 | */
112 | showsUserLocation?: boolean;
113 |
114 | /**
115 | * A Boolean value that determines whether to center the map on the user's
116 | * location.
117 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2973948-tracksuserlocation}
118 | */
119 | tracksUserLocation?: boolean;
120 |
121 | /**
122 | * Allows the user to zoom on the map by scrolling. Disabled by default.
123 | *
124 | * ⚠️ This feature relies on an undocumented MapKit JS feature. Support is not
125 | * guaranteed.
126 | */
127 | allowWheelToZoom?: boolean;
128 |
129 | /**
130 | * Include in the map only the given point of interest categories.
131 | *
132 | * Can't be used at the same time as excludedPOICategories.
133 | *
134 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/pointofinterestfilter/3585975-including}
135 | */
136 | includedPOICategories?: PointOfInterestCategory[];
137 |
138 | /**
139 | * Hide the given point of interest categories from the map.
140 | *
141 | * Can't be used at the same time as includedPOICategories.
142 | *
143 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/pointofinterestfilter/3585971-excluding}
144 | */
145 | excludedPOICategories?: PointOfInterestCategory[];
146 |
147 | /**
148 | * The amount of padding, in CSS pixels, to inset the map from the top edge.
149 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
150 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2977645-padding}
151 | */
152 | paddingTop?: number;
153 |
154 | /**
155 | * The amount of padding, in CSS pixels, to inset the map from the right edge.
156 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
157 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2977645-padding}
158 | */
159 | paddingRight?: number;
160 |
161 | /**
162 | * The amount of padding, in CSS pixels, to inset the map from the bottom edge.
163 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
164 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2977645-padding}
165 | */
166 | paddingBottom?: number;
167 |
168 | /**
169 | * The amount of padding, in CSS pixels, to inset the map from the left edge.
170 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
171 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/2977645-padding}
172 | */
173 | paddingLeft?: number;
174 |
175 | /**
176 | * The initial area that the map is showing.
177 | * Updates to this property after the map creation will not be reflected.
178 | */
179 | initialRegion?: CoordinateRegion;
180 |
181 | /**
182 | * A constraint of the location of the center of the map.
183 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/3257748-cameraboundary}
184 | */
185 | cameraBoundary?: CoordinateRegion;
186 |
187 | /**
188 | * The minimum allowed distance of the camera from the center of the map in meters.
189 | */
190 | minCameraDistance?: number;
191 |
192 | /**
193 | * The maximum allowed distance of the camera from the center of the map in meters.
194 | */
195 | maxCameraDistance?: number;
196 |
197 | /**
198 | * The map has loaded.
199 | */
200 | onLoad?: () => void;
201 |
202 | /**
203 | * The map’s visible region is about to change.
204 | */
205 | onRegionChangeStart?: (currentValue: CoordinateRegion) => void;
206 |
207 | /**
208 | * The map’s visible region finishes changing.
209 | */
210 | onRegionChangeEnd?: (newValue: CoordinateRegion) => void;
211 |
212 | /**
213 | * A program event or a user interaction causes the map’s type to change.
214 | */
215 | onMapTypeChange?: (newValue: MapType) => void;
216 |
217 | /**
218 | * A single tap occurs on the map outside an annotation or an overlay. If an
219 | * annotation or an overlay is in a selected state when a single tap occurs,
220 | * MapKit JS deselects the annotation or the overlay and dispatches a
221 | * single-tap event.
222 | */
223 | onSingleTap?: (event: MapInteractionEvent) => void;
224 |
225 | /**
226 | * A double tap occurs on the map without zooming the map.
227 | */
228 | onDoubleTap?: (event: MapInteractionEvent) => void;
229 |
230 | /**
231 | * A long press occurs on the map outside an annotation. A long press may be
232 | * the beginning of a panning or pinching gesture on the map. You can prevent
233 | * the gesture from starting by calling the preventDefault() method of the
234 | * event. Annotations need to be draggable to dispatch long-press events.
235 | */
236 | onLongPress?: (event: MapInteractionEvent) => void;
237 |
238 | /**
239 | * The browser's click event.
240 | */
241 | onClick?: (event: MapInteractionEvent) => void;
242 |
243 | /**
244 | * The browser's mouse move event.
245 | */
246 | onMouseMove?: (event: MapInteractionEvent) => void;
247 |
248 | /**
249 | * The browser's mouse down event.
250 | */
251 | onMouseDown?: (event: MapInteractionEvent) => void;
252 |
253 | /**
254 | * The browser's mouse up event.
255 | */
256 | onMouseUp?: (event: MapInteractionEvent) => void;
257 |
258 | /**
259 | * An event sent when `showsUserLocation` is true and the map acquires
260 | * the user’s location, or after an automatic update.
261 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/handling_map_events#2993302}
262 | */
263 | onUserLocationChange?: (event: UserLocationChangeEvent) => void;
264 |
265 | /**
266 | * An event sent when MapKit JS coudln't acquire the user’s location.
267 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/handling_map_events#2993302}
268 | */
269 | onUserLocationError?: (event: UserLocationErrorEvent) => void;
270 | }
271 |
--------------------------------------------------------------------------------
/src/components/Marker.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | useContext, useEffect, useLayoutEffect, useRef, useState,
3 | } from 'react';
4 | import { createPortal } from 'react-dom';
5 | import MapContext from '../context/MapContext';
6 | import { FeatureVisibility, toMapKitDisplayPriority, toMapKitFeatureVisibility } from '../util/parameters';
7 | import MarkerProps from './MarkerProps';
8 | import forwardMapkitEvent from '../util/forwardMapkitEvent';
9 | import CalloutContainer from './CalloutContainer';
10 |
11 | export default function Marker({
12 | latitude,
13 | longitude,
14 |
15 | title = '',
16 | subtitle = '',
17 | accessibilityLabel = null,
18 | subtitleVisibility = FeatureVisibility.Adaptive,
19 | titleVisibility = FeatureVisibility.Adaptive,
20 |
21 | clusteringIdentifier = null,
22 | displayPriority = undefined,
23 | collisionMode = undefined,
24 |
25 | color = '#ff5b40',
26 | glyphColor = 'white',
27 | glyphText = '',
28 | glyphImage = null,
29 | selectedGlyphImage = null,
30 |
31 | paddingTop = 0,
32 | paddingRight = 0,
33 | paddingBottom = 0,
34 | paddingLeft = 0,
35 | anchorOffsetX = 0,
36 | anchorOffsetY = 0,
37 |
38 | calloutElement = undefined,
39 | calloutContent = undefined,
40 | calloutLeftAccessory = undefined,
41 | calloutRightAccessory = undefined,
42 |
43 | calloutEnabled = undefined,
44 | calloutOffsetX = 0,
45 | calloutOffsetY = 0,
46 |
47 | selected = false,
48 | animates = true,
49 | appearanceAnimation = '',
50 | visible = true,
51 |
52 | draggable = false,
53 | enabled = true,
54 |
55 | onSelect = undefined,
56 | onDeselect = undefined,
57 | onDragStart = undefined,
58 | onDragEnd = undefined,
59 | onDragging = undefined,
60 | }: MarkerProps) {
61 | const [marker, setMarker] = useState(null);
62 | const map = useContext(MapContext);
63 |
64 | // Enum properties
65 | useEffect(() => {
66 | if (!marker) return;
67 | marker.subtitleVisibility = toMapKitFeatureVisibility(subtitleVisibility);
68 | }, [marker, subtitleVisibility]);
69 | useEffect(() => {
70 | if (!marker) return;
71 | marker.titleVisibility = toMapKitFeatureVisibility(titleVisibility);
72 | }, [marker, titleVisibility]);
73 |
74 | // Padding
75 | useEffect(() => {
76 | if (!marker) return;
77 | marker.padding = new mapkit.Padding(paddingTop, paddingRight, paddingBottom, paddingLeft);
78 | }, [marker, paddingTop, paddingRight, paddingBottom, paddingLeft]);
79 |
80 | // AnchorOffset
81 | useEffect(() => {
82 | if (!marker) return;
83 | marker.anchorOffset = new DOMPoint(anchorOffsetX, anchorOffsetY);
84 | }, [marker, anchorOffsetX, anchorOffsetY]);
85 |
86 | // CalloutOffset
87 | useEffect(() => {
88 | if (!marker) return;
89 | marker.calloutOffset = new DOMPoint(calloutOffsetX, calloutOffsetY);
90 | }, [marker, calloutOffsetX, calloutOffsetY]);
91 |
92 | const calloutLeftAccessoryRef = useRef(null);
93 | const calloutRightAccessoryRef = useRef(null);
94 | const calloutContentRef = useRef(null);
95 | const calloutElementRef = useRef(null);
96 |
97 | // Callout
98 | useLayoutEffect(() => {
99 | if (!marker) return;
100 |
101 | const callOutObj: mapkit.AnnotationCalloutDelegate = {};
102 | if (calloutElement && calloutElementRef.current !== null) {
103 | // @ts-expect-error
104 | callOutObj.calloutElementForAnnotation = () => calloutElementRef.current;
105 | }
106 | if (
107 | calloutLeftAccessory
108 | && calloutLeftAccessoryRef.current !== null
109 | ) {
110 | // @ts-expect-error
111 | callOutObj.calloutLeftAccessoryForAnnotation = () => calloutLeftAccessoryRef
112 | .current;
113 | }
114 | if (
115 | calloutRightAccessory
116 | && calloutRightAccessoryRef.current !== null
117 | ) {
118 | // @ts-expect-error
119 | callOutObj.calloutRightAccessoryForAnnotation = () => calloutRightAccessoryRef
120 | .current;
121 | }
122 | if (calloutContent && calloutContentRef.current !== null) {
123 | // @ts-expect-error
124 | callOutObj.calloutContentForAnnotation = () => calloutContentRef.current;
125 | }
126 | if (Object.keys(callOutObj).length > 0) {
127 | marker.callout = callOutObj;
128 | } else {
129 | // @ts-expect-error
130 | delete marker.callout;
131 | }
132 |
133 | // eslint-disable-next-line consistent-return
134 | return () => {
135 | // @ts-expect-error
136 | delete marker.callout;
137 | };
138 | }, [
139 | marker,
140 | calloutElement,
141 | calloutLeftAccessory,
142 | calloutRightAccessory,
143 | calloutContent,
144 | calloutElementRef.current,
145 | calloutLeftAccessoryRef.current,
146 | calloutRightAccessoryRef.current,
147 | calloutContentRef.current,
148 | ]);
149 |
150 | // Collision Mode
151 | useEffect(() => {
152 | if (!marker) return;
153 |
154 | if (collisionMode === 'Circle') {
155 | marker.collisionMode = mapkit.Annotation.CollisionMode.Circle;
156 | } else if (collisionMode === 'Rectangle') {
157 | marker.collisionMode = mapkit.Annotation.CollisionMode.Rectangle;
158 | } else {
159 | // @ts-ignore
160 | delete marker.collisionMode;
161 | }
162 | }, [marker, collisionMode]);
163 |
164 | // Display Priority
165 | useEffect(() => {
166 | if (!marker) return;
167 | // @ts-ignore
168 | if (displayPriority === undefined) { delete marker.displayPriority; return; }
169 | // @ts-ignore
170 | marker.displayPriority = toMapKitDisplayPriority(displayPriority);
171 | }, [marker, displayPriority]);
172 |
173 | // Simple values properties
174 | const properties = {
175 | title,
176 | subtitle,
177 | accessibilityLabel,
178 |
179 | color,
180 | glyphColor,
181 |
182 | glyphText,
183 | glyphImage,
184 | selectedGlyphImage,
185 |
186 | clusteringIdentifier,
187 |
188 | selected,
189 | animates,
190 | appearanceAnimation,
191 | draggable,
192 | enabled,
193 | visible,
194 |
195 | calloutEnabled,
196 | };
197 | Object.entries(properties).forEach(([propertyName, prop]) => {
198 | useEffect(() => {
199 | if (!marker) return;
200 | // @ts-ignore
201 | if (prop === undefined) { delete marker[propertyName]; return; }
202 | // @ts-ignore
203 | marker[propertyName] = prop;
204 | }, [marker, prop]);
205 | });
206 |
207 | // Events
208 | const handlerWithoutParameters = () => { };
209 | const events = [
210 | { name: 'select', handler: onSelect },
211 | { name: 'deselect', handler: onDeselect },
212 | { name: 'drag-start', handler: onDragStart },
213 | ] as const;
214 | events.forEach(({ name, handler }) => {
215 | forwardMapkitEvent(marker, name, handler, handlerWithoutParameters);
216 | });
217 |
218 | const dragEndParameters = () => ({
219 | latitude: marker!.coordinate.latitude,
220 | longitude: marker!.coordinate.longitude,
221 | });
222 | const draggingParameters = (e: { coordinate: mapkit.Coordinate }) => ({
223 | latitude: e.coordinate.latitude,
224 | longitude: e.coordinate.longitude,
225 | });
226 | forwardMapkitEvent(marker, 'drag-end', onDragEnd, dragEndParameters);
227 | forwardMapkitEvent(marker, 'dragging', onDragging, draggingParameters);
228 |
229 | // Coordinates
230 | useLayoutEffect(() => {
231 | if (map === null) return undefined;
232 |
233 | const m = new mapkit.MarkerAnnotation(
234 | new mapkit.Coordinate(latitude, longitude),
235 | );
236 | map.addAnnotation(m);
237 | setMarker(m);
238 |
239 | return () => {
240 | map.removeAnnotation(m);
241 | };
242 | }, [map, latitude, longitude]);
243 |
244 | return createPortal(
245 |
246 | {(calloutContent !== undefined) && (
247 |
251 | {calloutContent}
252 |
253 | )}
254 | {(calloutLeftAccessory !== undefined) && (
255 |
259 | {calloutLeftAccessory}
260 |
261 | )}
262 | {(calloutRightAccessory !== undefined) && (
263 |
267 | {calloutRightAccessory}
268 |
269 | )}
270 | {(calloutElement !== undefined) && (
271 |
275 | {calloutElement}
276 |
277 | )}
278 |
,
279 | document.body,
280 | );
281 | }
282 |
--------------------------------------------------------------------------------
/src/components/MarkerProps.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 | import { Coordinate, FeatureVisibility } from '../util/parameters';
3 |
4 | export default interface MarkerProps {
5 | /**
6 | * The latitude in degrees.
7 | */
8 | latitude: number;
9 |
10 | /**
11 | * The longitude in degrees.
12 | */
13 | longitude: number;
14 |
15 | /**
16 | * The text to display in the annotation’s callout.
17 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotation/2973835-title}
18 | */
19 | title?: string;
20 |
21 | /**
22 | * The text to display as a subtitle on the second line of an annotation’s callout.
23 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotation/2973834-subtitle}
24 | */
25 | subtitle?: string;
26 |
27 | /**
28 | * Accessibility text for the annotation.
29 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotation/2973814-accessibilitylabel}
30 | */
31 | accessibilityLabel?: string | null;
32 |
33 | /**
34 | * A value that determines the behavior of the subtitle’s visibility.
35 | * @see {@link https://developer.apple.com/documentation/mapkitjs/markerannotation/3009407-subtitlevisibility}
36 | */
37 | subtitleVisibility?: FeatureVisibility;
38 |
39 | /**
40 | * A value that determines the behavior of the title’s visibility.
41 | * @see {@link https://developer.apple.com/documentation/mapkitjs/markerannotation/3009408-titlevisibility}
42 | */
43 | titleVisibility?: FeatureVisibility;
44 |
45 | /**
46 | * The background color of the balloon.
47 | * @see {@link https://developer.apple.com/documentation/mapkitjs/markerannotation/2973987-color}
48 | */
49 | color?: string;
50 |
51 | /**
52 | * The fill color of the glyph.
53 | * @see {@link https://developer.apple.com/documentation/mapkitjs/markerannotation/2973988-glyphcolor}
54 | */
55 | glyphColor?: string;
56 |
57 | /**
58 | * The text to display in the marker balloon.
59 | * @see {@link https://developer.apple.com/documentation/mapkitjs/markerannotation/2978331-glyphtext}
60 | */
61 | glyphText?: string;
62 |
63 | /**
64 | * The image to display in the marker balloon.
65 | * @see {@link https://developer.apple.com/documentation/mapkitjs/markerannotation/2973989-glyphimage}
66 | */
67 | glyphImage?: object | null;
68 |
69 | /**
70 | * The image to display in the balloon when the user selects the marker.
71 | * @see {@link https://developer.apple.com/documentation/mapkitjs/markerannotation/2973991-selectedglyphimage}
72 | */
73 | selectedGlyphImage?: object | null;
74 |
75 | /**
76 | * The amount of padding, in CSS pixels, to inset the map from the top edge.
77 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
78 | */
79 | paddingTop?: number;
80 |
81 | /**
82 | * The amount of padding, in CSS pixels, to inset the map from the right edge.
83 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
84 | */
85 | paddingRight?: number;
86 |
87 | /**
88 | * The amount of padding, in CSS pixels, to inset the map from the bottom edge.
89 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
90 | */
91 | paddingBottom?: number;
92 |
93 | /**
94 | * The amount of padding, in CSS pixels, to inset the map from the left edge.
95 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/padding}
96 | */
97 | paddingLeft?: number;
98 |
99 | /**
100 | * An X offset that changes the annotation’s default anchor point.
101 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973816-anchoroffset}
102 | */
103 | anchorOffsetX?: number;
104 |
105 | /**
106 | * An Y offset that changes the annotation’s default anchor point.
107 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973816-anchoroffset}
108 | */
109 | anchorOffsetY?: number;
110 |
111 | /**
112 | * A Boolean value that determines whether the map displays the marker in a selected state.
113 | */
114 | selected?: boolean;
115 |
116 | /**
117 | * A Boolean value that determines whether the map animates the marker.
118 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973817-animates}
119 | */
120 | animates?: boolean;
121 |
122 | /**
123 | * A CSS animation that runs when the marker appears on the map.
124 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973818-appearanceanimation}
125 | */
126 | appearanceAnimation?: string;
127 |
128 | /**
129 | * A Boolean value that determines whether the user can drag the marker.
130 | *
131 | * (Marker needs to be enabled in order to be draggable.)
132 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973817-animates}
133 | */
134 | draggable?: boolean;
135 |
136 | /**
137 | * A Boolean value that determines whether the annotation responds to user interaction.
138 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973828-enabled}
139 | */
140 | enabled?: boolean;
141 |
142 | /**
143 | * A Boolean value that determines whether the annotation is visible or hidden.
144 | */
145 | visible?: boolean;
146 |
147 | /**
148 | * Event fired when the marker is selected.
149 | */
150 | onSelect?: () => void;
151 |
152 | /**
153 | * Event fired when the marker is deselected.
154 | */
155 | onDeselect?: () => void;
156 |
157 | /**
158 | * Event fired with the user initiates a drag for the annotation.
159 | */
160 | onDragStart?: () => void;
161 |
162 | /**
163 | * Event fired with the user ends a drag for the annotation.
164 | */
165 | onDragEnd?: (newPosition: Coordinate) => void;
166 |
167 | /**
168 | * Event fired when the user a drags the annotation.
169 | */
170 | onDragging?: (newPosition: Coordinate) => void;
171 |
172 | /**
173 | * A shared identifier for all of the member annotations.
174 | * An annotation needs a clusteringIdentifier to be part of an annotation cluster.
175 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotations/clustering_annotations}
176 | */
177 | clusteringIdentifier?: string | null;
178 |
179 | /**
180 | * A mode that determines the shape of the collision frame.
181 | * Rectangle | Circle | None
182 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973822-collisionmode}
183 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/collisionmode}
184 | */
185 | collisionMode?: 'Rectangle' | 'Circle' | null;
186 |
187 | /**
188 | * A numeric hint that the map uses to prioritize how it displays annotations.
189 | *
190 | * Is either any number from `0` to `1000`,
191 | * or a preset value: `"low"` (250), `"high"` (750), or `"required"` (1000).
192 | *
193 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973825-displaypriority}
194 | */
195 | displayPriority?: number | 'low' | 'high' | 'required';
196 |
197 | /**
198 | * An X offset that changes the annotation callout’s default placement.
199 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973821-calloutoffset}
200 | */
201 | calloutOffsetX?: number;
202 |
203 | /**
204 | * An Y offset that changes the annotation callout’s default placement.
205 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973821-calloutoffset}
206 | */
207 | calloutOffsetY?: number;
208 |
209 | /**
210 | * A Boolean value that determines whether the map shows an annotation’s callout.
211 | * If the title is empty, the framework can’t show the standard callout even if property is true.
212 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/annotation/2973820-calloutenabled}
213 | */
214 | calloutEnabled?: boolean;
215 |
216 | /**
217 | * Returns an element to use as a custom accessory on the left side of the callout content area.
218 | *
219 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotationcalloutdelegate/2991150-calloutleftaccessoryforannotatio}
220 | */
221 | calloutLeftAccessory?: ReactNode;
222 |
223 | /**
224 | * Returns an element to use as a custom accessory on the right side of the callout content area.
225 | *
226 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotationcalloutdelegate/2991151-calloutrightaccessoryforannotati}
227 | */
228 | calloutRightAccessory?: ReactNode;
229 |
230 | /**
231 | * Returns custom content for the callout bubble.
232 | *
233 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotationcalloutdelegate/2991148-calloutcontentforannotation}
234 | */
235 | calloutContent?: ReactNode;
236 |
237 | /**
238 | * Returns an element representing a custom callout.
239 | *
240 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotationcalloutdelegate/2991148-calloutcontentforannotation}
241 | */
242 | calloutElement?: ReactNode;
243 |
244 | }
245 |
--------------------------------------------------------------------------------
/src/components/Polygon.tsx:
--------------------------------------------------------------------------------
1 | import { useContext, useEffect, useState } from 'react';
2 | import MapContext from '../context/MapContext';
3 | import PolygonProps from './PolygonProps';
4 | import { Coordinate } from '../util/parameters';
5 |
6 | export default function Polygon({
7 | points,
8 |
9 | visible = true,
10 | enabled = true,
11 | selected = false,
12 |
13 | onSelect = undefined,
14 | onDeselect = undefined,
15 |
16 | lineDash = [],
17 | lineDashOffset = 0,
18 | lineJoin = 'round',
19 | lineWidth = 1,
20 |
21 | strokeColor = 'rgb(0, 122, 255)',
22 | strokeOpacity = 1,
23 | strokeStart = 0,
24 | strokeEnd = 1,
25 |
26 | fillColor = 'rgb(0, 122, 255)',
27 | fillOpacity = 0.1,
28 | fillRule = 'nonzero',
29 | }: PolygonProps) {
30 | const [polygon, setPolygon] = useState(null);
31 | const map = useContext(MapContext);
32 |
33 | useEffect(() => {
34 | if (map === null) return undefined;
35 |
36 | const overlay = new mapkit.PolygonOverlay([]);
37 | map.addOverlay(overlay);
38 | setPolygon(overlay);
39 |
40 | return () => {
41 | map.removeOverlay(overlay);
42 | };
43 | }, [map]);
44 |
45 | // Points
46 | useEffect(() => {
47 | if (polygon === null) return;
48 |
49 | // No points
50 | if (!points || points.length === 0) {
51 | polygon.points = [];
52 | return;
53 | }
54 |
55 | const toMapKitCoordinates = (coordinates: Coordinate[]): mapkit.Coordinate[] => coordinates.map(
56 | ({ latitude, longitude }) => new mapkit.Coordinate(latitude, longitude),
57 | );
58 |
59 | // @ts-ignore
60 | polygon.points = Array.isArray(points[0])
61 | ? (points as Coordinate[][]).map(toMapKitCoordinates)
62 | : toMapKitCoordinates(points as Coordinate[]);
63 | }, [polygon, points]);
64 |
65 | // Simple properties
66 | const properties = { visible, enabled, selected };
67 | Object.entries(properties).forEach(([propertyName, prop]) => {
68 | useEffect(() => {
69 | if (!polygon) return;
70 | // @ts-ignore
71 | polygon[propertyName] = prop;
72 | }, [polygon, prop]);
73 | });
74 |
75 | // Simple style properties
76 | const styleProperties = {
77 | lineDash,
78 | lineDashOffset,
79 | lineJoin,
80 | lineWidth,
81 |
82 | strokeColor,
83 | strokeOpacity,
84 | strokeStart,
85 | strokeEnd,
86 |
87 | fillColor,
88 | fillOpacity,
89 | fillRule,
90 | };
91 | Object.entries(styleProperties).forEach(([propertyName, prop]) => {
92 | useEffect(() => {
93 | if (!polygon) return;
94 | // @ts-ignore
95 | polygon.style[propertyName] = prop;
96 | }, [polygon, prop]);
97 | });
98 |
99 | // Events
100 | const events = [
101 | { name: 'select', handler: onSelect },
102 | { name: 'deselect', handler: onDeselect },
103 | ] as const;
104 | events.forEach(({ name, handler }) => {
105 | useEffect(() => {
106 | if (!polygon || !handler) return undefined;
107 |
108 | const handlerWithoutParameters = () => handler();
109 |
110 | polygon.addEventListener(name, handlerWithoutParameters);
111 | return () => polygon.removeEventListener(name, handlerWithoutParameters);
112 | }, [polygon, handler]);
113 | });
114 |
115 | return null;
116 | }
117 |
--------------------------------------------------------------------------------
/src/components/PolygonProps.tsx:
--------------------------------------------------------------------------------
1 | import { Coordinate } from '../util/parameters';
2 |
3 | export default interface PolygonProps {
4 | /**
5 | * One or more arrays of coordinates that define the polygon overlay shape.
6 | * @see {@link https://developer.apple.com/documentation/mapkitjs/polygonoverlay/2974011-points}
7 | */
8 | points: Coordinate[] | Coordinate[][];
9 |
10 | /**
11 | * A Boolean value that determines whether the polygon is visible.
12 | * @see {@link https://developer.apple.com/documentation/mapkitjs/overlay/2974002-visible}
13 | */
14 | visible?: boolean;
15 |
16 | /**
17 | * A Boolean value that determines whether the polygon responds to user interaction.
18 | * @see {@link https://developer.apple.com/documentation/mapkitjs/overlay/2973997-enabled}
19 | */
20 | enabled?: boolean;
21 |
22 | /**
23 | * A Boolean value that determines whether the map displays the polygon in a selected state.
24 | * @see {@link https://developer.apple.com/documentation/mapkitjs/overlay/2974000-selected}
25 | */
26 | selected?: boolean;
27 |
28 | /**
29 | * Event fired when the polygon is selected.
30 | */
31 | onSelect?: () => void;
32 |
33 | /**
34 | * Event fired when the polygon is deselected.
35 | */
36 | onDeselect?: () => void;
37 |
38 | /**
39 | * An array of line and gap lengths for creating a dashed line.
40 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974025-linedash}
41 | */
42 | lineDash?: number[];
43 |
44 | /**
45 | * The number of CSS pixels to use as an offset when drawing a line’s dash pattern.
46 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974026-linedashoffset}
47 | */
48 | lineDashOffset?: number;
49 |
50 | /**
51 | * The corner style to apply when joining line segments.
52 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974027-linejoin}
53 | */
54 | lineJoin?: 'miter' | 'round' | 'bevel';
55 |
56 | /**
57 | * The width of a line’s stroke, in CSS pixels.
58 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974028-linewidth}
59 | */
60 | lineWidth?: number;
61 |
62 | /**
63 | * The stroke color of a line.
64 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974030-strokecolor}
65 | */
66 | strokeColor?: string;
67 |
68 | /**
69 | * The opacity of the stroke color.
70 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974031-strokeopacity}
71 | */
72 | strokeOpacity?: number;
73 |
74 | /**
75 | * The unit distance along the line where a stroke begins.
76 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/3586011-strokestart}
77 | */
78 | strokeStart?: number;
79 |
80 | /**
81 | * The unit distance along the line where a stroke ends.
82 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/3586010-strokeend}
83 | */
84 | strokeEnd?: number;
85 |
86 | /**
87 | * The fill color of a shape.
88 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974021-fillcolor}
89 | */
90 | fillColor?: string | null;
91 |
92 | /**
93 | * The opacity of the fill color.
94 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974022-fillopacity}
95 | */
96 | fillOpacity?: number;
97 |
98 | /**
99 | * A rule for determining whether a point is inside or outside a polygon.
100 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974023-fillrule}
101 | */
102 | fillRule?: 'nonzero' | 'evenodd';
103 | }
104 |
--------------------------------------------------------------------------------
/src/components/Polyline.tsx:
--------------------------------------------------------------------------------
1 | import { useContext, useEffect, useState } from 'react';
2 | import MapContext from '../context/MapContext';
3 | import PolylineProps from './PolylineProps';
4 |
5 | export default function Polyline({
6 | points,
7 |
8 | visible = true,
9 | enabled = true,
10 | selected = false,
11 |
12 | onSelect = undefined,
13 | onDeselect = undefined,
14 |
15 | lineCap = 'round',
16 | lineDash = [],
17 | lineDashOffset = 0,
18 | lineJoin = 'round',
19 | lineWidth = 1,
20 |
21 | strokeColor = 'rgb(0, 122, 255)',
22 | strokeOpacity = 1,
23 | strokeStart = 0,
24 | strokeEnd = 1,
25 | }: PolylineProps) {
26 | const [polyline, setPolyline] = useState(null);
27 | const map = useContext(MapContext);
28 |
29 | useEffect(() => {
30 | if (map === null) return undefined;
31 |
32 | const overlay = new mapkit.PolylineOverlay([]);
33 | map.addOverlay(overlay);
34 | setPolyline(overlay);
35 |
36 | return () => {
37 | map.removeOverlay(overlay);
38 | };
39 | }, [map]);
40 |
41 | // Points
42 | useEffect(() => {
43 | if (polyline === null) return;
44 | polyline.points = points
45 | .map(({ latitude, longitude }) => new mapkit.Coordinate(latitude, longitude));
46 | }, [polyline, points]);
47 |
48 | // Simple properties
49 | const properties = { visible, enabled, selected };
50 | Object.entries(properties).forEach(([propertyName, prop]) => {
51 | useEffect(() => {
52 | if (!polyline) return;
53 | // @ts-ignore
54 | polyline[propertyName] = prop;
55 | }, [polyline, prop]);
56 | });
57 |
58 | // Simple style properties
59 | const styleProperties = {
60 | lineCap,
61 | lineDash,
62 | lineDashOffset,
63 | lineJoin,
64 | lineWidth,
65 |
66 | strokeColor,
67 | strokeOpacity,
68 | strokeStart,
69 | strokeEnd,
70 | };
71 | Object.entries(styleProperties).forEach(([propertyName, prop]) => {
72 | useEffect(() => {
73 | if (!polyline) return;
74 | // @ts-ignore
75 | polyline.style[propertyName] = prop;
76 | }, [polyline, prop]);
77 | });
78 |
79 | // Events
80 | const events = [
81 | { name: 'select', handler: onSelect },
82 | { name: 'deselect', handler: onDeselect },
83 | ] as const;
84 | events.forEach(({ name, handler }) => {
85 | useEffect(() => {
86 | if (!polyline || !handler) return undefined;
87 |
88 | const handlerWithoutParameters = () => handler();
89 |
90 | polyline.addEventListener(name, handlerWithoutParameters);
91 | return () => polyline.removeEventListener(name, handlerWithoutParameters);
92 | }, [polyline, handler]);
93 | });
94 |
95 | return null;
96 | }
97 |
--------------------------------------------------------------------------------
/src/components/PolylineProps.tsx:
--------------------------------------------------------------------------------
1 | import { Coordinate } from '../util/parameters';
2 |
3 | export default interface PolylineProps {
4 | /**
5 | * An array of coordinate points that define the polyline overlay’s shape.
6 | * @see {@link https://developer.apple.com/documentation/mapkitjs/polylineoverlay/2974014-points}
7 | */
8 | points: Coordinate[];
9 |
10 | /**
11 | * A Boolean value that determines whether the polyline is visible.
12 | * @see {@link https://developer.apple.com/documentation/mapkitjs/overlay/2974002-visible}
13 | */
14 | visible?: boolean;
15 |
16 | /**
17 | * A Boolean value that determines whether the polyline responds to user interaction.
18 | * @see {@link https://developer.apple.com/documentation/mapkitjs/overlay/2973997-enabled}
19 | */
20 | enabled?: boolean;
21 |
22 | /**
23 | * A Boolean value that determines whether the map displays the polyline in a selected state.
24 | * @see {@link https://developer.apple.com/documentation/mapkitjs/overlay/2974000-selected}
25 | */
26 | selected?: boolean;
27 |
28 | /**
29 | * Event fired when the polyline is selected.
30 | */
31 | onSelect?: () => void;
32 |
33 | /**
34 | * Event fired when the polyline is deselected.
35 | */
36 | onDeselect?: () => void;
37 |
38 | /**
39 | * The style to use when drawing line endings.
40 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974024-linecap}
41 | */
42 | lineCap?: 'butt' | 'round' | 'square';
43 |
44 | /**
45 | * An array of line and gap lengths for creating a dashed line.
46 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974025-linedash}
47 | */
48 | lineDash?: number[];
49 |
50 | /**
51 | * The number of CSS pixels to use as an offset when drawing a line’s dash pattern.
52 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974026-linedashoffset}
53 | */
54 | lineDashOffset?: number;
55 |
56 | /**
57 | * The corner style to apply when joining line segments.
58 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974027-linejoin}
59 | */
60 | lineJoin?: 'miter' | 'round' | 'bevel';
61 |
62 | /**
63 | * The width of a line’s stroke, in CSS pixels.
64 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974028-linewidth}
65 | */
66 | lineWidth?: number;
67 |
68 | /**
69 | * The stroke color of a line.
70 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974030-strokecolor}
71 | */
72 | strokeColor?: string;
73 |
74 | /**
75 | * The opacity of the stroke color.
76 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/2974031-strokeopacity}
77 | */
78 | strokeOpacity?: number;
79 |
80 | /**
81 | * The unit distance along the line where a stroke begins.
82 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/3586011-strokestart}
83 | */
84 | strokeStart?: number;
85 |
86 | /**
87 | * The unit distance along the line where a stroke ends.
88 | * @see {@link https://developer.apple.com/documentation/mapkitjs/style/3586010-strokeend}
89 | */
90 | strokeEnd?: number;
91 | }
92 |
--------------------------------------------------------------------------------
/src/context/MapContext.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default React.createContext(null);
4 |
--------------------------------------------------------------------------------
/src/events.ts:
--------------------------------------------------------------------------------
1 | import { Coordinate } from './util/parameters';
2 |
3 | /**
4 | * The details of an event caused by taps on the map.
5 | */
6 | export interface MapInteractionEvent {
7 | /**
8 | * A DOM point with the coordinates (x, y) of the event on the page.
9 | */
10 | readonly pointOnPage: { x: number, y: number };
11 |
12 | /**
13 | * An array of DOM event objects listing the pertinent low-level events that
14 | * led to the recognized gesture. You can inspect these and tailor the code to
15 | * react according to the additional low-level events, such as modifier keys
16 | * for the events.
17 | */
18 | readonly domEvents: Event[];
19 |
20 | /**
21 | * Gets the map coordinates of the event’s location.
22 | */
23 | toCoordinates(): Coordinate;
24 | }
25 |
26 | /**
27 | * Event sent when `showsUserLocation` is true and the map acquires the user’s
28 | * location, or after an automatic update.
29 | */
30 | export interface UserLocationChangeEvent {
31 | /**
32 | * The current location of the user.
33 | */
34 | coordinate: Coordinate;
35 |
36 | /**
37 | * The time corresponding to the location acquisition.
38 | */
39 | timestamp: Date;
40 |
41 | /**
42 | * A value indicating the current floor number of the user (either a number
43 | * or undefined), or null if the browser doesn’t expose ths property.
44 | */
45 | floorLevel: number | undefined | null;
46 | }
47 |
48 | /**
49 | * A code indicating why location acquisition failed.
50 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/handling_map_events#2993302}
51 | */
52 | export enum UserLocationError {
53 | /**
54 | * The user refuses permission to obtain location information.
55 | */
56 | PERMISSION_DENIED = 1,
57 |
58 | /**
59 | * The geolocation API returns an error.
60 | */
61 | POSITION_UNAVAILABLE = 2,
62 |
63 | /**
64 | * The operation times out without acquiring the location.
65 | */
66 | TIMEOUT = 3,
67 |
68 | /**
69 | * The system hasn’t initialized MapKit JS.
70 | */
71 | MAPKIT_NOT_INITIALIZED = 4,
72 | }
73 |
74 | /**
75 | * The details of the event emitted when MapKit JS is unable to acquire
76 | * the user's location.
77 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/handling_map_events#2993302}
78 | */
79 | export interface UserLocationErrorEvent {
80 | /**
81 | * A code indicating why location acquisition failed.
82 | */
83 | code: UserLocationError;
84 |
85 | /**
86 | * A a human-readable string for the developer.
87 | * This message isn’t for display to the user.
88 | */
89 | message: String;
90 | }
91 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Map } from './components/Map';
2 | export type { default as MapProps } from './components/MapProps';
3 |
4 | export { default as Marker } from './components/Marker';
5 | export type { default as MarkerProps } from './components/MarkerProps';
6 |
7 | export { default as Annotation } from './components/Annotation';
8 | export type { default as AnnotationProps } from './components/AnnotationProps';
9 |
10 | export { default as Polyline } from './components/Polyline';
11 | export type { default as PolylineProps } from './components/PolylineProps';
12 |
13 | export { default as Polygon } from './components/Polygon';
14 | export type { default as PolygonProps } from './components/PolygonProps';
15 |
16 | export {
17 | ColorScheme, MapType, Distances, LoadPriority, FeatureVisibility,
18 | PointOfInterestCategory,
19 | } from './util/parameters';
20 | export type { Coordinate, CoordinateRegion } from './util/parameters';
21 |
22 | export type {
23 | MapInteractionEvent,
24 | UserLocationChangeEvent, UserLocationError, UserLocationErrorEvent,
25 | } from './events';
26 |
--------------------------------------------------------------------------------
/src/stories/Annotation.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import { fn } from '@storybook/test';
4 |
5 | import Map from '../components/Map';
6 | import Annotation from '../components/Annotation';
7 | import { CoordinateRegion, FeatureVisibility } from '../util/parameters';
8 |
9 | // @ts-ignore
10 | const token = import.meta.env.STORYBOOK_MAPKIT_JS_TOKEN!;
11 |
12 | // SVG from https://webkul.github.io/vivid
13 | function CustomMarker() {
14 | return (
15 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
48 | const enumArgType = (e: object) => ({
49 | options: Object.values(e).filter((x) => typeof x === 'string'),
50 | mapping: e,
51 | });
52 | export default {
53 | title: 'Components/Annotation',
54 | component: Annotation,
55 | args: {
56 | onSelect: fn(),
57 | onDeselect: fn(),
58 | onDragStart: fn(),
59 | onDragEnd: fn(),
60 | onDragging: fn(),
61 | },
62 | argTypes: {
63 | subtitleVisibility: enumArgType(FeatureVisibility),
64 | titleVisibility: enumArgType(FeatureVisibility),
65 | },
66 | parameters: {
67 | layout: 'fullscreen',
68 | },
69 | } as Meta;
70 |
71 | type MarkerProps = React.ComponentProps;
72 |
73 | const Template: StoryFn = (args) => {
74 | const initialRegion: CoordinateRegion = useMemo(
75 | () => ({
76 | centerLatitude: 48,
77 | centerLongitude: 14,
78 | latitudeDelta: 22,
79 | longitudeDelta: 55,
80 | }),
81 | [],
82 | );
83 | return (
84 |
85 |
86 | Click me
87 |
88 |
89 | );
90 | };
91 |
92 | export const Default = Template.bind({});
93 | Default.args = {
94 | latitude: 46.52,
95 | longitude: 6.57,
96 | size: { width: 100, height: 24 },
97 | title: 'Hello World',
98 | };
99 |
100 | const CustomMarkerAnnotationTemplate: StoryFn = (args) => {
101 | const initialRegion: CoordinateRegion = useMemo(
102 | () => ({
103 | centerLatitude: 48,
104 | centerLongitude: 14,
105 | latitudeDelta: 22,
106 | longitudeDelta: 55,
107 | }),
108 | [],
109 | );
110 | return (
111 |
112 |
113 |
114 |
115 |
116 | );
117 | };
118 |
119 | export const CustomMarkerAnnotation = CustomMarkerAnnotationTemplate.bind({});
120 | CustomMarkerAnnotation.args = { latitude: 46.52, longitude: 6.57 };
121 |
122 | CustomMarkerAnnotation.storyName = 'Custom Marker Annotation';
123 |
124 | export const MoveableAnnotation = Template.bind({});
125 | MoveableAnnotation.args = {
126 | latitude: 46.52,
127 | longitude: 6.57,
128 | title: 'Tap and hold to move',
129 | draggable: true,
130 | enabled: true,
131 | };
132 |
133 | MoveableAnnotation.storyName = 'Moveable Annotation';
134 |
135 | export const AnimatedAnnotation = () => {
136 | const initialRegion: CoordinateRegion = useMemo(
137 | () => ({
138 | centerLatitude: 46.20738751546706,
139 | centerLongitude: 6.155891756231,
140 | latitudeDelta: 0.007,
141 | longitudeDelta: 0.015,
142 | }),
143 | [],
144 | );
145 |
146 | return (
147 |
148 |
155 |
156 |
157 |
158 | );
159 | };
160 |
161 | AnimatedAnnotation.storyName = 'Animated Annotation';
162 |
163 | function CustomCalloutElement({
164 | title,
165 | subtitle,
166 | url,
167 | }: {
168 | title: string;
169 | subtitle: string;
170 | url: string;
171 | }) {
172 | return (
173 |
174 |
{title ?? ''}
175 |
183 |
184 | );
185 | }
186 |
187 | export const CustomAnnotationCallout = () => {
188 | const initialRegion: CoordinateRegion = useMemo(
189 | () => ({
190 | centerLatitude: 46.20738751546706,
191 | centerLongitude: 6.155891756231,
192 | latitudeDelta: 0.007,
193 | longitudeDelta: 0.015,
194 | }),
195 | [],
196 | );
197 |
198 | return (
199 |
200 |
211 | )}
212 | calloutEnabled
213 | calloutOffsetX={-148}
214 | calloutOffsetY={-82}
215 | >
216 |
217 |
218 |
219 | );
220 | };
221 | CustomAnnotationCallout.storyName = 'Annotation with custom callout element';
222 |
--------------------------------------------------------------------------------
/src/stories/Map.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { useId, useMemo, useState } from 'react';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import { fn } from '@storybook/test';
4 | import './stories.css';
5 |
6 | import Map from '../components/Map';
7 | import {
8 | ColorScheme,
9 | MapType,
10 | Distances,
11 | LoadPriority,
12 | CoordinateRegion,
13 | PointOfInterestCategory,
14 | FeatureVisibility,
15 | } from '../util/parameters';
16 | import Marker from '../components/Marker';
17 | import { MapInteractionEvent } from '..';
18 |
19 | // @ts-ignore
20 | const token = import.meta.env.STORYBOOK_MAPKIT_JS_TOKEN!;
21 |
22 | const enumArgType = (e: object) => ({
23 | options: Object.values(e).filter((val) => typeof val === 'number'),
24 | control: {
25 | type: 'inline-radio',
26 | labels: Object.values(e).filter((val) => typeof val === 'string'),
27 | },
28 | });
29 |
30 | export default {
31 | title: 'Components/Map',
32 | component: Map,
33 | args: {
34 | token,
35 | onLoad: fn(),
36 | onRegionChangeStart: fn(),
37 | onRegionChangeEnd: fn(),
38 | onMapTypeChange: fn(),
39 | onSingleTap: fn(),
40 | onDoubleTap: fn(),
41 | onLongPress: fn(),
42 | onClick: fn(),
43 | onMouseMove: fn(),
44 | onMouseDown: fn(),
45 | onMouseUp: fn(),
46 | onUserLocationChange: fn(),
47 | onUserLocationError: fn(),
48 | },
49 | argTypes: {
50 | colorScheme: enumArgType(ColorScheme),
51 | mapType: enumArgType(MapType),
52 | distances: enumArgType(Distances),
53 | loadPriority: enumArgType(LoadPriority),
54 | showsCompass: enumArgType(FeatureVisibility),
55 | showsScale: enumArgType(FeatureVisibility),
56 | },
57 | parameters: { layout: 'fullscreen' },
58 | } as Meta;
59 |
60 | type MapProps = React.ComponentProps;
61 |
62 | const Template: StoryFn = (args) => ;
63 |
64 | export const Empty = Template.bind({});
65 |
66 | export const FixedSize: StoryFn = (args) => (
67 |
68 |
69 |
70 | );
71 |
72 | export const CustomizedAppearance = Template.bind({});
73 | CustomizedAppearance.args = {
74 | colorScheme: ColorScheme.Dark,
75 | mapType: MapType.MutedStandard,
76 | showsMapTypeControl: false,
77 | showsUserLocationControl: true,
78 | paddingLeft: 16,
79 | paddingRight: 16,
80 | paddingTop: 32,
81 | paddingBottom: 32,
82 | };
83 |
84 | export const RegionLock = Template.bind({});
85 | RegionLock.args = {
86 | cameraBoundary: {
87 | centerLatitude: 40.444,
88 | centerLongitude: -79.945,
89 | latitudeDelta: 0.006,
90 | longitudeDelta: 0.008,
91 | },
92 | initialRegion: {
93 | centerLatitude: 40.44316701238923,
94 | centerLongitude: -79.9431147637379,
95 | latitudeDelta: 0.006337455593801167,
96 | longitudeDelta: 0.011960061265583022,
97 | },
98 | minCameraDistance: 100,
99 | maxCameraDistance: 1000,
100 | };
101 |
102 | export const LiveStateUpdate = () => {
103 | const [theme, setTheme] = useState(ColorScheme.Light);
104 |
105 | const options: { name: string; value: ColorScheme; id: string }[] = [
106 | { name: 'Light', value: ColorScheme.Light, id: useId() },
107 | { name: 'Dark', value: ColorScheme.Dark, id: useId() },
108 | { name: 'Auto', value: ColorScheme.Auto, id: useId() },
109 | ];
110 |
111 | return (
112 | <>
113 |
114 |
115 |
116 |
117 | {options.map((option) => (
118 |
119 | setTheme(option.value)}
125 | checked={theme === option.value}
126 | />
127 | {option.name}
128 |
129 | ))}
130 |
131 |
132 | >
133 | );
134 | };
135 |
136 | export const MapInteractionEvents = () => {
137 | type MarkerData = {
138 | latitude: number;
139 | longitude: number;
140 | title: string;
141 | color: string;
142 | };
143 |
144 | const [markers, setMarkers] = useState([]);
145 |
146 | const eventHandler = (title: string, color: string) => (e: MapInteractionEvent) => {
147 | const { latitude, longitude } = e.toCoordinates();
148 | const newMarker: MarkerData = {
149 | latitude,
150 | longitude,
151 | title,
152 | color,
153 | };
154 | setMarkers([...markers, newMarker]);
155 | };
156 |
157 | return (
158 |
165 | {markers.map(({
166 | latitude, longitude, title, color,
167 | }, index) => (
168 |
177 | ))}
178 |
179 | );
180 | };
181 |
182 | export const PointOfInterestFilters = () => {
183 | const initialRegion: CoordinateRegion = useMemo(
184 | () => ({
185 | centerLatitude: 40.7538,
186 | centerLongitude: -73.986,
187 | latitudeDelta: 0.03,
188 | longitudeDelta: 0.03,
189 | }),
190 | [],
191 | );
192 |
193 | const categories: PointOfInterestCategory[] = useMemo(
194 | () => (
195 | Object.values(PointOfInterestCategory) as Array<
196 | keyof typeof PointOfInterestCategory
197 | >
198 | )
199 | .filter((val) => typeof val === 'string')
200 | .map((str) => PointOfInterestCategory[str]),
201 | [],
202 | );
203 |
204 | const [isEnabled, setIsEnabled] = useState(() => categories.map(() => true));
205 |
206 | const idPrefix = useId();
207 |
208 | return (
209 | <>
210 | isEnabled[index],
216 | )}
217 | />
218 |
219 |
220 |
221 | {categories.map((category, categoryIndex) => (
222 |
223 | {
228 | const newIsEnabled = [...isEnabled];
229 | newIsEnabled[categoryIndex] = !isEnabled[categoryIndex];
230 | setIsEnabled(newIsEnabled);
231 | }}
232 | />
233 | {PointOfInterestCategory[category]}
234 |
235 | ))}
236 |
237 |
238 | >
239 | );
240 | };
241 | PointOfInterestFilters.storyName = 'Point of Interest Filter';
242 |
243 | function ReadOnlyInput({ label, value }: { label: string; value: string }) {
244 | const id = useId();
245 | return (
246 |
247 | {label}
248 |
249 |
250 | );
251 | }
252 |
253 | export const RegionChangeEvent = () => {
254 | const [centerLatitude, setCenterLatitude] = useState(46.94869130019719);
255 | const [centerLongitude, setCenterLongitude] = useState(7.447300186911917);
256 | const [latitudeDelta, setLatitudeDelta] = useState(0.010188625378894756);
257 | const [longitudeDelta, setLongitudeDelta] = useState(0.024314821659999097);
258 |
259 | const initialRegion: CoordinateRegion = useMemo(
260 | () => ({
261 | centerLatitude,
262 | centerLongitude,
263 | latitudeDelta,
264 | longitudeDelta,
265 | }),
266 | [],
267 | );
268 |
269 | return (
270 | <>
271 | {
275 | setCenterLatitude(region.centerLatitude);
276 | setCenterLongitude(region.centerLongitude);
277 | setLatitudeDelta(region.latitudeDelta);
278 | setLongitudeDelta(region.longitudeDelta);
279 | }}
280 | />
281 |
282 |
283 |
284 |
288 |
292 |
296 |
300 |
301 |
302 | >
303 | );
304 | };
305 |
306 | export const CustomLoadFunction = () => {
307 | const initialRegion: CoordinateRegion = useMemo(
308 | () => ({
309 | centerLatitude: 40.7538,
310 | centerLongitude: -73.986,
311 | latitudeDelta: 0.03,
312 | longitudeDelta: 0.03,
313 | }),
314 | [],
315 | );
316 |
317 | return (
318 | new Promise((resolve) => {
320 | const element = document.createElement('script');
321 | // @ts-ignore-next-line
322 | window.initMapKit = () => {
323 | // @ts-ignore-next-line
324 | delete window.initMapKit;
325 | window.mapkit.init({
326 | authorizationCallback: (done) => {
327 | done(customLoadToken);
328 | },
329 | });
330 | resolve();
331 | };
332 | element.src = 'https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.core.js';
333 | element.dataset.callback = 'initMapKit';
334 | element.dataset.initialToken = customLoadToken;
335 | element.dataset.libraries = 'map';
336 | element.crossOrigin = 'anonymous';
337 | document.head.appendChild(element);
338 | })}
339 | token={token}
340 | initialRegion={initialRegion}
341 | showsMapTypeControl={false}
342 | />
343 | );
344 | };
345 | CustomLoadFunction.storyName = 'Custom `load` Function';
346 |
--------------------------------------------------------------------------------
/src/stories/Marker.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { useId, useMemo, useState } from 'react';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import { fn } from '@storybook/test';
4 | import './stories.css';
5 | import Map from '../components/Map';
6 | import Marker from '../components/Marker';
7 | import { CoordinateRegion, FeatureVisibility } from '../util/parameters';
8 |
9 | // @ts-ignore
10 | const token = import.meta.env.STORYBOOK_MAPKIT_JS_TOKEN!;
11 |
12 | const enumArgType = (e: object) => ({
13 | options: Object.values(e).filter((x) => typeof x === 'string'),
14 | mapping: e,
15 | });
16 | export default {
17 | title: 'Components/Marker',
18 | component: Marker,
19 | args: {
20 | onSelect: fn(),
21 | onDeselect: fn(),
22 | onDragStart: fn(),
23 | onDragEnd: fn(),
24 | onDragging: fn(),
25 | },
26 | argTypes: {
27 | subtitleVisibility: enumArgType(FeatureVisibility),
28 | titleVisibility: enumArgType(FeatureVisibility),
29 | },
30 | parameters: {
31 | layout: 'fullscreen',
32 | },
33 | } as Meta;
34 |
35 | type MarkerProps = React.ComponentProps;
36 |
37 | const Template: StoryFn = (args) => {
38 | const initialRegion: CoordinateRegion = useMemo(
39 | () => ({
40 | centerLatitude: 48,
41 | centerLongitude: 14,
42 | latitudeDelta: 22,
43 | longitudeDelta: 55,
44 | }),
45 | [],
46 | );
47 | return (
48 |
49 |
50 |
51 | );
52 | };
53 |
54 | export const Default = Template.bind({});
55 | Default.args = { latitude: 46.52, longitude: 6.57 };
56 |
57 | export const TwoWayBindingSelected = () => {
58 | const [selected, setSelected] = useState(false);
59 |
60 | const initialRegion: CoordinateRegion = useMemo(
61 | () => ({
62 | centerLatitude: 46.20738751546706,
63 | centerLongitude: 6.155891756231,
64 | latitudeDelta: 0.007,
65 | longitudeDelta: 0.015,
66 | }),
67 | [],
68 | );
69 |
70 | const checkboxId = useId();
71 |
72 | return (
73 | <>
74 |
75 | setSelected(true)}
82 | onDeselect={() => setSelected(false)}
83 | />
84 |
85 |
86 |
87 |
88 |
89 | setSelected(e.target.checked)}
94 | />
95 | Selected
96 |
97 |
98 |
99 | >
100 | );
101 | };
102 | TwoWayBindingSelected.storyName = 'Two-Way Binding for `selected`';
103 |
104 | export const MoveableMarker = () => {
105 | const [latitude, setLatitude] = useState(46.20738751546706);
106 | const [longitude, setLongitude] = useState(6.155891756231);
107 |
108 | const idLatitude = useId();
109 | const idLongitude = useId();
110 |
111 | const initialRegion: CoordinateRegion = useMemo(
112 | () => ({
113 | centerLatitude: 46.20738751546706,
114 | centerLongitude: 6.155891756231,
115 | latitudeDelta: 0.01,
116 | longitudeDelta: 0.01,
117 | }),
118 | [],
119 | );
120 |
121 | return (
122 | <>
123 |
124 | {
131 | setLatitude(coordinate.latitude);
132 | setLongitude(coordinate.longitude);
133 | }}
134 | />
135 |
136 |
137 |
161 | >
162 | );
163 | };
164 |
165 | export const MarkerClustering = () => {
166 | const clusteringIdentifier = 'id';
167 | const [selected, setSelected] = useState(null);
168 |
169 | const initialRegion: CoordinateRegion = useMemo(
170 | () => ({
171 | centerLatitude: 46.20738751546706,
172 | centerLongitude: 6.155891756231,
173 | latitudeDelta: 1,
174 | longitudeDelta: 1,
175 | }),
176 | [],
177 | );
178 |
179 | const coordinates = [
180 | { latitude: 46.20738751546706, longitude: 6.155891756231 },
181 | { latitude: 46.25738751546706, longitude: 6.185891756231 },
182 | { latitude: 46.28738751546706, longitude: 6.2091756231 },
183 | ];
184 |
185 | return (
186 | <>
187 |
188 | {coordinates.map(({ latitude, longitude }, index) => (
189 | setSelected(index + 1)}
195 | onDeselect={() => setSelected(null)}
196 | clusteringIdentifier={clusteringIdentifier}
197 | collisionMode="Circle"
198 | displayPriority={750}
199 | />
200 | ))}
201 |
202 |
203 |
204 |
205 |
{selected ? `Selected marker #${selected}` : 'Not selected'}
206 |
207 |
208 | >
209 | );
210 | };
211 |
212 | MarkerClustering.storyName = 'Clustering three markers into one';
213 |
214 | function CustomCalloutElement({
215 | title,
216 | subtitle,
217 | url,
218 | }: {
219 | title: string;
220 | subtitle: string;
221 | url: string;
222 | }) {
223 | return (
224 |
225 |
{title ?? ''}
226 |
234 |
235 | );
236 | }
237 |
238 | export const CustomMarkerCallout = () => {
239 | const initialRegion: CoordinateRegion = useMemo(
240 | () => ({
241 | centerLatitude: 46.20738751546706,
242 | centerLongitude: 6.155891756231,
243 | latitudeDelta: 0.007,
244 | longitudeDelta: 0.015,
245 | }),
246 | [],
247 | );
248 |
249 | return (
250 |
251 |
262 | )}
263 | calloutEnabled
264 | calloutOffsetX={-148}
265 | calloutOffsetY={-78}
266 | />
267 |
268 | );
269 | };
270 | CustomMarkerCallout.storyName = 'Marker with custom callout element';
271 |
272 | function CustomCalloutContent({
273 | title,
274 | subtitle,
275 | }: {
276 | title: string;
277 | subtitle: string;
278 | }) {
279 | return (
280 |
281 |
{title ?? ''}
282 |
{subtitle ?? ''}
283 |
284 | );
285 | }
286 |
287 | function CustomCalloutLeftAccessory({ src }: { src: string }) {
288 | return (
289 |
290 |
291 |
292 | );
293 | }
294 |
295 | function CustomCalloutRightAccessory({ url }: { url: string }) {
296 | return (
297 |
317 | );
318 | }
319 |
320 | export const CustomMarkerCalloutContent = () => {
321 | const initialRegion: CoordinateRegion = useMemo(
322 | () => ({
323 | centerLatitude: 46.20738751546706,
324 | centerLongitude: 6.155891756231,
325 | latitudeDelta: 0.007,
326 | longitudeDelta: 0.015,
327 | }),
328 | [],
329 | );
330 |
331 | return (
332 |
333 |
343 | )}
344 | calloutLeftAccessory={
345 |
346 | }
347 | calloutRightAccessory={
348 |
349 | }
350 | calloutEnabled
351 | />
352 |
353 | );
354 | };
355 | CustomMarkerCalloutContent.storyName = 'Marker with custom callout content';
356 |
--------------------------------------------------------------------------------
/src/stories/Polygon.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import { fn } from '@storybook/test';
4 |
5 | import Map from '../components/Map';
6 | import Polygon from '../components/Polygon';
7 | import { CoordinateRegion } from '..';
8 |
9 | // @ts-ignore
10 | const token = import.meta.env.STORYBOOK_MAPKIT_JS_TOKEN!;
11 |
12 | export default {
13 | title: 'Components/Polygon',
14 | component: Polygon,
15 | parameters: {
16 | layout: 'fullscreen',
17 | },
18 | args: {
19 | onSelect: fn(),
20 | onDeselect: fn(),
21 | },
22 | } as Meta;
23 |
24 | type PolygonProps = React.ComponentProps;
25 |
26 | const Template: StoryFn = (args) => {
27 | const initialRegion: CoordinateRegion = useMemo(
28 | () => ({
29 | centerLatitude: 46.5185,
30 | centerLongitude: 6.568,
31 | latitudeDelta: 0.0035,
32 | longitudeDelta: 0.0035,
33 | }),
34 | [],
35 | );
36 | return (
37 |
43 |
44 |
45 | );
46 | };
47 |
48 | export const Default = Template.bind({});
49 | Default.args = {
50 | lineWidth: 2,
51 |
52 | points: [
53 | // Map data from OpenStreetMap (https://www.openstreetmap.org/relation/331569)
54 | [
55 | { longitude: 6.5678349, latitude: 46.5188959 },
56 | { longitude: 6.5676762, latitude: 46.5188915 },
57 | { longitude: 6.5672481, latitude: 46.5188885 },
58 | { longitude: 6.5672513, latitude: 46.5186939 },
59 | { longitude: 6.5672514, latitude: 46.5186761 },
60 | { longitude: 6.5672514, latitude: 46.5186588 },
61 | { longitude: 6.5672548, latitude: 46.5184279 },
62 | { longitude: 6.5672551, latitude: 46.5184048 },
63 | { longitude: 6.5672561, latitude: 46.5183349 },
64 | { longitude: 6.5672586, latitude: 46.5181608 },
65 | { longitude: 6.5672595, latitude: 46.5180962 },
66 | { longitude: 6.5672598, latitude: 46.5180758 },
67 | { longitude: 6.5672602, latitude: 46.518049 },
68 | { longitude: 6.5672607, latitude: 46.5180154 },
69 | { longitude: 6.5672609, latitude: 46.5180032 },
70 | { longitude: 6.5672612, latitude: 46.5179772 },
71 | { longitude: 6.5672618, latitude: 46.517952 },
72 | { longitude: 6.5672638, latitude: 46.5177956 },
73 | { longitude: 6.5676192, latitude: 46.517799 },
74 | { longitude: 6.5676452, latitude: 46.5177992 },
75 | { longitude: 6.567819, latitude: 46.5178005 },
76 | { longitude: 6.5678322, latitude: 46.5178008 },
77 | { longitude: 6.567895, latitude: 46.5178021 },
78 | { longitude: 6.5679913, latitude: 46.5178018 },
79 | { longitude: 6.5680328, latitude: 46.5178021 },
80 | { longitude: 6.5682897, latitude: 46.517804 },
81 | { longitude: 6.5683866, latitude: 46.5178047 },
82 | { longitude: 6.568566, latitude: 46.5178061 },
83 | { longitude: 6.5685746, latitude: 46.5178062 },
84 | { longitude: 6.568944, latitude: 46.5178088 },
85 | { longitude: 6.5689892, latitude: 46.5178091 },
86 | { longitude: 6.5690173, latitude: 46.5178093 },
87 | { longitude: 6.5693116, latitude: 46.5178116 },
88 | { longitude: 6.569359, latitude: 46.517812 },
89 | { longitude: 6.5694347, latitude: 46.5178126 },
90 | { longitude: 6.5694336, latitude: 46.5178843 },
91 | { longitude: 6.5694331, latitude: 46.5179167 },
92 | { longitude: 6.5694284, latitude: 46.5182131 },
93 | { longitude: 6.5694263, latitude: 46.5183493 },
94 | { longitude: 6.5694257, latitude: 46.5183861 },
95 | { longitude: 6.5694218, latitude: 46.518632 },
96 | { longitude: 6.5694175, latitude: 46.518903 },
97 | { longitude: 6.568526, latitude: 46.5188978 },
98 | { longitude: 6.5683254, latitude: 46.5188974 },
99 | { longitude: 6.568288, latitude: 46.5188966 },
100 | { longitude: 6.5682393, latitude: 46.5188955 },
101 | { longitude: 6.5679224, latitude: 46.5188935 },
102 | { longitude: 6.5679136, latitude: 46.5188932 },
103 | { longitude: 6.5678699, latitude: 46.5188947 },
104 | { longitude: 6.5678349, latitude: 46.5188959 },
105 | ],
106 | [
107 | { longitude: 6.5679495, latitude: 46.5180242 },
108 | { longitude: 6.5679711, latitude: 46.5180132 },
109 | { longitude: 6.5679908, latitude: 46.5179938 },
110 | { longitude: 6.5679991, latitude: 46.5179667 },
111 | { longitude: 6.5679981, latitude: 46.5179381 },
112 | { longitude: 6.5679952, latitude: 46.51791 },
113 | { longitude: 6.5679792, latitude: 46.5178902 },
114 | { longitude: 6.5679632, latitude: 46.5178792 },
115 | { longitude: 6.5679421, latitude: 46.5178707 },
116 | { longitude: 6.5679138, latitude: 46.5178655 },
117 | { longitude: 6.5678841, latitude: 46.5178655 },
118 | { longitude: 6.5678511, latitude: 46.5178731 },
119 | { longitude: 6.567823, latitude: 46.5178884 },
120 | { longitude: 6.5678087, latitude: 46.5179067 },
121 | { longitude: 6.5678048, latitude: 46.5179261 },
122 | { longitude: 6.5678051, latitude: 46.517982 },
123 | { longitude: 6.5678147, latitude: 46.5180053 },
124 | { longitude: 6.5678329, latitude: 46.5180213 },
125 | { longitude: 6.5678566, latitude: 46.5180306 },
126 | { longitude: 6.5678796, latitude: 46.5180339 },
127 | { longitude: 6.5679136, latitude: 46.5180341 },
128 | { longitude: 6.5679495, latitude: 46.5180242 },
129 | ],
130 | [
131 | { longitude: 6.5678845, latitude: 46.5182645 },
132 | { longitude: 6.5679074, latitude: 46.5182503 },
133 | { longitude: 6.5679241, latitude: 46.5182271 },
134 | { longitude: 6.5679251, latitude: 46.5181923 },
135 | { longitude: 6.5679124, latitude: 46.5181692 },
136 | { longitude: 6.5678882, latitude: 46.5181483 },
137 | { longitude: 6.5678399, latitude: 46.5181347 },
138 | { longitude: 6.5677921, latitude: 46.5181339 },
139 | { longitude: 6.5677412, latitude: 46.5181408 },
140 | { longitude: 6.5676725, latitude: 46.518159 },
141 | { longitude: 6.5676325, latitude: 46.5181786 },
142 | { longitude: 6.5676019, latitude: 46.5182069 },
143 | { longitude: 6.5675972, latitude: 46.518229 },
144 | { longitude: 6.5676004, latitude: 46.5182474 },
145 | { longitude: 6.5676142, latitude: 46.5182703 },
146 | { longitude: 6.5676337, latitude: 46.5182851 },
147 | { longitude: 6.5676578, latitude: 46.5182966 },
148 | { longitude: 6.5676968, latitude: 46.5183047 },
149 | { longitude: 6.5677241, latitude: 46.5183062 },
150 | { longitude: 6.5677597, latitude: 46.5183026 },
151 | { longitude: 6.5678086, latitude: 46.5182921 },
152 | { longitude: 6.5678585, latitude: 46.5182765 },
153 | { longitude: 6.5678706, latitude: 46.518271 },
154 | { longitude: 6.5678845, latitude: 46.5182645 },
155 | ],
156 | [
157 | { longitude: 6.5673784, latitude: 46.5180812 },
158 | { longitude: 6.56737, latitude: 46.5180816 },
159 | { longitude: 6.5673488, latitude: 46.5180882 },
160 | { longitude: 6.5673368, latitude: 46.5180965 },
161 | { longitude: 6.56733, latitude: 46.518113 },
162 | { longitude: 6.5673286, latitude: 46.5181377 },
163 | { longitude: 6.5673301, latitude: 46.5181651 },
164 | { longitude: 6.5673354, latitude: 46.5181783 },
165 | { longitude: 6.5673463, latitude: 46.5181921 },
166 | { longitude: 6.5673665, latitude: 46.5182032 },
167 | { longitude: 6.5673898, latitude: 46.5182067 },
168 | { longitude: 6.56741, latitude: 46.5182036 },
169 | { longitude: 6.5674263, latitude: 46.518198 },
170 | { longitude: 6.5674428, latitude: 46.5181873 },
171 | { longitude: 6.5674455, latitude: 46.5181832 },
172 | { longitude: 6.5674528, latitude: 46.5181718 },
173 | { longitude: 6.5674569, latitude: 46.5181612 },
174 | { longitude: 6.5674596, latitude: 46.5181328 },
175 | { longitude: 6.5674564, latitude: 46.5181122 },
176 | { longitude: 6.567445, latitude: 46.5180967 },
177 | { longitude: 6.5674342, latitude: 46.5180878 },
178 | { longitude: 6.5674123, latitude: 46.5180827 },
179 | { longitude: 6.5673942, latitude: 46.5180805 },
180 | { longitude: 6.5673784, latitude: 46.5180812 },
181 | ],
182 | [
183 | { longitude: 6.5687679, latitude: 46.5180717 },
184 | { longitude: 6.5687757, latitude: 46.5181237 },
185 | { longitude: 6.5688076, latitude: 46.5181748 },
186 | { longitude: 6.5688393, latitude: 46.5182058 },
187 | { longitude: 6.5688719, latitude: 46.5182297 },
188 | { longitude: 6.5689234, latitude: 46.5182521 },
189 | { longitude: 6.5689973, latitude: 46.5182725 },
190 | { longitude: 6.56908, latitude: 46.5182791 },
191 | { longitude: 6.5691703, latitude: 46.5182732 },
192 | { longitude: 6.5692368, latitude: 46.5182494 },
193 | { longitude: 6.5693026, latitude: 46.5182056 },
194 | { longitude: 6.5693412, latitude: 46.5181419 },
195 | { longitude: 6.5693571, latitude: 46.5180724 },
196 | { longitude: 6.5693388, latitude: 46.5179985 },
197 | { longitude: 6.5692982, latitude: 46.5179493 },
198 | { longitude: 6.5692392, latitude: 46.5179123 },
199 | { longitude: 6.5691841, latitude: 46.5178911 },
200 | { longitude: 6.5690972, latitude: 46.5178875 },
201 | { longitude: 6.5690067, latitude: 46.5178973 },
202 | { longitude: 6.5689281, latitude: 46.5179181 },
203 | { longitude: 6.5688636, latitude: 46.5179543 },
204 | { longitude: 6.5688169, latitude: 46.51799 },
205 | { longitude: 6.5687807, latitude: 46.5180286 },
206 | { longitude: 6.5687679, latitude: 46.5180717 },
207 | ],
208 | [
209 | { longitude: 6.5683752, latitude: 46.5179681 },
210 | { longitude: 6.5683978, latitude: 46.5179657 },
211 | { longitude: 6.5684275, latitude: 46.5179689 },
212 | { longitude: 6.5684476, latitude: 46.5179772 },
213 | { longitude: 6.5684732, latitude: 46.5179905 },
214 | { longitude: 6.5684952, latitude: 46.5179968 },
215 | { longitude: 6.5685226, latitude: 46.5179958 },
216 | { longitude: 6.5685489, latitude: 46.5179874 },
217 | { longitude: 6.568568, latitude: 46.5179723 },
218 | { longitude: 6.5685751, latitude: 46.5179474 },
219 | { longitude: 6.5685662, latitude: 46.517927 },
220 | { longitude: 6.5685471, latitude: 46.5179144 },
221 | { longitude: 6.5685157, latitude: 46.5179027 },
222 | { longitude: 6.5684783, latitude: 46.5179073 },
223 | { longitude: 6.5684521, latitude: 46.5179189 },
224 | { longitude: 6.5684329, latitude: 46.5179266 },
225 | { longitude: 6.5684113, latitude: 46.5179307 },
226 | { longitude: 6.5683815, latitude: 46.5179299 },
227 | { longitude: 6.5683547, latitude: 46.5179227 },
228 | { longitude: 6.5683354, latitude: 46.5179118 },
229 | { longitude: 6.5683137, latitude: 46.5179024 },
230 | { longitude: 6.5682909, latitude: 46.5178993 },
231 | { longitude: 6.5682646, latitude: 46.5179015 },
232 | { longitude: 6.5682416, latitude: 46.5179107 },
233 | { longitude: 6.5682288, latitude: 46.5179204 },
234 | { longitude: 6.5682191, latitude: 46.5179439 },
235 | { longitude: 6.5682219, latitude: 46.5179588 },
236 | { longitude: 6.568229, latitude: 46.517974 },
237 | { longitude: 6.5682407, latitude: 46.5179851 },
238 | { longitude: 6.5682622, latitude: 46.5179937 },
239 | { longitude: 6.568291, latitude: 46.5179962 },
240 | { longitude: 6.5683214, latitude: 46.5179919 },
241 | { longitude: 6.5683336, latitude: 46.5179861 },
242 | { longitude: 6.568355, latitude: 46.5179755 },
243 | { longitude: 6.5683752, latitude: 46.5179681 },
244 | ],
245 | [
246 | { longitude: 6.5683179, latitude: 46.5187933 },
247 | { longitude: 6.5683167, latitude: 46.5187827 },
248 | { longitude: 6.5683111, latitude: 46.5187713 },
249 | { longitude: 6.5682999, latitude: 46.5187623 },
250 | { longitude: 6.5682755, latitude: 46.5187556 },
251 | { longitude: 6.5682503, latitude: 46.5187588 },
252 | { longitude: 6.5682349, latitude: 46.5187672 },
253 | { longitude: 6.568224, latitude: 46.5187817 },
254 | { longitude: 6.5682253, latitude: 46.5187969 },
255 | { longitude: 6.5682388, latitude: 46.518812 },
256 | { longitude: 6.5682565, latitude: 46.518821 },
257 | { longitude: 6.5682775, latitude: 46.5188231 },
258 | { longitude: 6.5682962, latitude: 46.5188184 },
259 | { longitude: 6.5683093, latitude: 46.5188093 },
260 | { longitude: 6.5683179, latitude: 46.5187933 },
261 | ],
262 | [
263 | { longitude: 6.5687945, latitude: 46.5185275 },
264 | { longitude: 6.568812, latitude: 46.5185276 },
265 | { longitude: 6.5688287, latitude: 46.5185273 },
266 | { longitude: 6.5688721, latitude: 46.518518 },
267 | { longitude: 6.5688959, latitude: 46.5185 },
268 | { longitude: 6.5689149, latitude: 46.5184812 },
269 | { longitude: 6.5689233, latitude: 46.5184581 },
270 | { longitude: 6.5689249, latitude: 46.5184356 },
271 | { longitude: 6.5689194, latitude: 46.5184123 },
272 | { longitude: 6.5689018, latitude: 46.5183887 },
273 | { longitude: 6.5688614, latitude: 46.5183643 },
274 | { longitude: 6.5688063, latitude: 46.5183438 },
275 | { longitude: 6.5687556, latitude: 46.5183308 },
276 | { longitude: 6.5687097, latitude: 46.5183273 },
277 | { longitude: 6.568668, latitude: 46.5183311 },
278 | { longitude: 6.5686289, latitude: 46.5183388 },
279 | { longitude: 6.5685928, latitude: 46.5183561 },
280 | { longitude: 6.5685696, latitude: 46.5183828 },
281 | { longitude: 6.5685663, latitude: 46.518418 },
282 | { longitude: 6.5685814, latitude: 46.5184445 },
283 | { longitude: 6.568609, latitude: 46.5184675 },
284 | { longitude: 6.5686673, latitude: 46.5184952 },
285 | { longitude: 6.5687401, latitude: 46.5185204 },
286 | { longitude: 6.5687945, latitude: 46.5185275 },
287 | ],
288 | [
289 | { longitude: 6.5690837, latitude: 46.5185237 },
290 | { longitude: 6.569111, latitude: 46.5185204 },
291 | { longitude: 6.5691313, latitude: 46.5185098 },
292 | { longitude: 6.5691384, latitude: 46.5184926 },
293 | { longitude: 6.569136, latitude: 46.5184763 },
294 | { longitude: 6.5691265, latitude: 46.518464 },
295 | { longitude: 6.5691003, latitude: 46.518455 },
296 | { longitude: 6.5690706, latitude: 46.5184558 },
297 | { longitude: 6.5690504, latitude: 46.5184624 },
298 | { longitude: 6.5690385, latitude: 46.5184771 },
299 | { longitude: 6.5690361, latitude: 46.5184959 },
300 | { longitude: 6.569048, latitude: 46.5185123 },
301 | { longitude: 6.5690659, latitude: 46.5185221 },
302 | { longitude: 6.5690837, latitude: 46.5185237 },
303 | ],
304 | [
305 | { longitude: 6.5685775, latitude: 46.5187607 },
306 | { longitude: 6.5686156, latitude: 46.5187629 },
307 | { longitude: 6.5686607, latitude: 46.5187564 },
308 | { longitude: 6.5686881, latitude: 46.5187433 },
309 | { longitude: 6.5687095, latitude: 46.5187253 },
310 | { longitude: 6.5687154, latitude: 46.518695 },
311 | { longitude: 6.568713, latitude: 46.5186787 },
312 | { longitude: 6.5687059, latitude: 46.5186615 },
313 | { longitude: 6.5686881, latitude: 46.5186451 },
314 | { longitude: 6.5686595, latitude: 46.5186312 },
315 | { longitude: 6.5686135, latitude: 46.5186252 },
316 | { longitude: 6.568579, latitude: 46.5186246 },
317 | { longitude: 6.5685316, latitude: 46.518631 },
318 | { longitude: 6.568496, latitude: 46.5186515 },
319 | { longitude: 6.5684781, latitude: 46.5186785 },
320 | { longitude: 6.5684793, latitude: 46.5187038 },
321 | { longitude: 6.5684962, latitude: 46.5187296 },
322 | { longitude: 6.5685264, latitude: 46.5187482 },
323 | { longitude: 6.5685775, latitude: 46.5187607 },
324 | ],
325 | [
326 | { longitude: 6.5676448, latitude: 46.5185685 },
327 | { longitude: 6.5676637, latitude: 46.5185543 },
328 | { longitude: 6.5676664, latitude: 46.5185363 },
329 | { longitude: 6.5676606, latitude: 46.5185231 },
330 | { longitude: 6.5676429, latitude: 46.5185087 },
331 | { longitude: 6.5676207, latitude: 46.5185045 },
332 | { longitude: 6.5675999, latitude: 46.5185065 },
333 | { longitude: 6.5675839, latitude: 46.5185158 },
334 | { longitude: 6.5675729, latitude: 46.518528 },
335 | { longitude: 6.5675692, latitude: 46.5185411 },
336 | { longitude: 6.5675749, latitude: 46.5185565 },
337 | { longitude: 6.5675893, latitude: 46.5185665 },
338 | { longitude: 6.5676162, latitude: 46.5185735 },
339 | { longitude: 6.5676448, latitude: 46.5185685 },
340 | ],
341 | [
342 | { longitude: 6.567965, latitude: 46.518354 },
343 | { longitude: 6.567949, latitude: 46.5183587 },
344 | { longitude: 6.5679031, latitude: 46.5183791 },
345 | { longitude: 6.5678734, latitude: 46.5184029 },
346 | { longitude: 6.5678522, latitude: 46.5184318 },
347 | { longitude: 6.567849, latitude: 46.5184615 },
348 | { longitude: 6.5678551, latitude: 46.5184878 },
349 | { longitude: 6.5678746, latitude: 46.5185081 },
350 | { longitude: 6.5679038, latitude: 46.5185279 },
351 | { longitude: 6.5679307, latitude: 46.518542 },
352 | { longitude: 6.5679883, latitude: 46.5185589 },
353 | { longitude: 6.5680614, latitude: 46.5185699 },
354 | { longitude: 6.5681494, latitude: 46.5185767 },
355 | { longitude: 6.5682365, latitude: 46.5185757 },
356 | { longitude: 6.5682938, latitude: 46.5185661 },
357 | { longitude: 6.5683395, latitude: 46.5185449 },
358 | { longitude: 6.5683645, latitude: 46.5185246 },
359 | { longitude: 6.5683765, latitude: 46.518497 },
360 | { longitude: 6.5683836, latitude: 46.5184684 },
361 | { longitude: 6.5683797, latitude: 46.5184454 },
362 | { longitude: 6.5683729, latitude: 46.5184218 },
363 | { longitude: 6.5683581, latitude: 46.5183977 },
364 | { longitude: 6.5683361, latitude: 46.5183784 },
365 | { longitude: 6.5683063, latitude: 46.518358 },
366 | { longitude: 6.5682879, latitude: 46.5183478 },
367 | { longitude: 6.568261, latitude: 46.5183368 },
368 | { longitude: 6.5682289, latitude: 46.5183301 },
369 | { longitude: 6.5681609, latitude: 46.5183249 },
370 | { longitude: 6.5680906, latitude: 46.5183282 },
371 | { longitude: 6.5680292, latitude: 46.5183381 },
372 | { longitude: 6.567965, latitude: 46.518354 },
373 | ],
374 | [
375 | { longitude: 6.5676715, latitude: 46.518808 },
376 | { longitude: 6.5677232, latitude: 46.5188103 },
377 | { longitude: 6.5677669, latitude: 46.5187999 },
378 | { longitude: 6.567807, latitude: 46.518781 },
379 | { longitude: 6.5678382, latitude: 46.5187575 },
380 | { longitude: 6.5678585, latitude: 46.5187325 },
381 | { longitude: 6.5678645, latitude: 46.5187073 },
382 | { longitude: 6.5678666, latitude: 46.5187029 },
383 | { longitude: 6.5678665, latitude: 46.518687 },
384 | { longitude: 6.5678627, latitude: 46.5186634 },
385 | { longitude: 6.5678487, latitude: 46.5186452 },
386 | { longitude: 6.5678356, latitude: 46.518634 },
387 | { longitude: 6.5677946, latitude: 46.5186129 },
388 | { longitude: 6.5677377, latitude: 46.5186025 },
389 | { longitude: 6.5676938, latitude: 46.5186022 },
390 | { longitude: 6.5676436, latitude: 46.5186223 },
391 | { longitude: 6.56761, latitude: 46.5186493 },
392 | { longitude: 6.5675848, latitude: 46.5186868 },
393 | { longitude: 6.5675748, latitude: 46.5187222 },
394 | { longitude: 6.5675862, latitude: 46.5187608 },
395 | { longitude: 6.56762, latitude: 46.518793 },
396 | { longitude: 6.5676715, latitude: 46.518808 },
397 | ],
398 | [
399 | { longitude: 6.5690955, latitude: 46.518719 },
400 | { longitude: 6.5690895, latitude: 46.518701 },
401 | { longitude: 6.5690765, latitude: 46.5186911 },
402 | { longitude: 6.5690551, latitude: 46.5186838 },
403 | { longitude: 6.5690289, latitude: 46.5186854 },
404 | { longitude: 6.5690075, latitude: 46.5186944 },
405 | { longitude: 6.5689944, latitude: 46.5187083 },
406 | { longitude: 6.568992, latitude: 46.5187239 },
407 | { longitude: 6.5689992, latitude: 46.5187402 },
408 | { longitude: 6.5690158, latitude: 46.5187501 },
409 | { longitude: 6.5690432, latitude: 46.5187541 },
410 | { longitude: 6.5690681, latitude: 46.5187492 },
411 | { longitude: 6.5690883, latitude: 46.5187361 },
412 | { longitude: 6.5690955, latitude: 46.518719 },
413 | ],
414 | [
415 | { longitude: 6.5683824, latitude: 46.5182274 },
416 | { longitude: 6.5684004, latitude: 46.518249 },
417 | { longitude: 6.5684322, latitude: 46.5182604 },
418 | { longitude: 6.5684674, latitude: 46.5182607 },
419 | { longitude: 6.5685001, latitude: 46.5182457 },
420 | { longitude: 6.568513, latitude: 46.5182279 },
421 | { longitude: 6.5685102, latitude: 46.5182088 },
422 | { longitude: 6.5684919, latitude: 46.5181901 },
423 | { longitude: 6.5684597, latitude: 46.5181787 },
424 | { longitude: 6.5684322, latitude: 46.5181773 },
425 | { longitude: 6.56841, latitude: 46.5181813 },
426 | { longitude: 6.5683916, latitude: 46.5181894 },
427 | { longitude: 6.5683809, latitude: 46.5182076 },
428 | { longitude: 6.5683824, latitude: 46.5182274 },
429 | ],
430 | ],
431 | };
432 |
--------------------------------------------------------------------------------
/src/stories/Polyline.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import { fn } from '@storybook/test';
4 |
5 | import Map from '../components/Map';
6 | import Polyline from '../components/Polyline';
7 | import { ColorScheme, CoordinateRegion } from '..';
8 |
9 | // @ts-ignore
10 | const token = import.meta.env.STORYBOOK_MAPKIT_JS_TOKEN!;
11 |
12 | export default {
13 | title: 'Components/Polyline',
14 | component: Polyline,
15 | parameters: {
16 | layout: 'fullscreen',
17 | },
18 | args: {
19 | onSelect: fn(),
20 | onDeselect: fn(),
21 | },
22 | } as Meta;
23 |
24 | type PolylineProps = React.ComponentProps;
25 |
26 | const Template: StoryFn = (args) => {
27 | const initialRegion: CoordinateRegion = useMemo(
28 | () => ({
29 | centerLatitude: 46.52,
30 | centerLongitude: 6.57,
31 | latitudeDelta: 0.07,
32 | longitudeDelta: 0.03,
33 | }),
34 | [],
35 | );
36 | return (
37 |
42 |
43 |
44 | );
45 | };
46 |
47 | export const Default = Template.bind({});
48 | Default.args = {
49 | lineWidth: 5,
50 | lineDash: [15, 5],
51 | lineCap: 'butt',
52 |
53 | strokeColor: '#ffcc02',
54 |
55 | points: [
56 | { longitude: 6.51871, latitude: 46.51821 },
57 | { longitude: 6.52363, latitude: 46.51768 },
58 | { longitude: 6.52502, latitude: 46.51755 },
59 | { longitude: 6.52518, latitude: 46.51749 },
60 | { longitude: 6.52996, latitude: 46.51664 },
61 | { longitude: 6.53456, latitude: 46.51569 },
62 | { longitude: 6.53542, latitude: 46.51545 },
63 | { longitude: 6.53697, latitude: 46.51509 },
64 | { longitude: 6.53955, latitude: 46.51428 },
65 | { longitude: 6.53972, latitude: 46.51427 },
66 | { longitude: 6.54241, latitude: 46.51354 },
67 | { longitude: 6.54498, latitude: 46.5133 },
68 | { longitude: 6.5457, latitude: 46.51331 },
69 | { longitude: 6.54876, latitude: 46.51371 },
70 | { longitude: 6.55429, latitude: 46.51383 },
71 | { longitude: 6.5576, latitude: 46.51401 },
72 | { longitude: 6.56135, latitude: 46.5155 },
73 | { longitude: 6.56553, latitude: 46.51701 },
74 | { longitude: 6.56617, latitude: 46.51706 },
75 | { longitude: 6.56987, latitude: 46.51739 },
76 | { longitude: 6.57218, latitude: 46.518 },
77 | { longitude: 6.57291, latitude: 46.51824 },
78 | { longitude: 6.5776, latitude: 46.51958 },
79 | { longitude: 6.58353, latitude: 46.52068 },
80 | { longitude: 6.58515, latitude: 46.52113 },
81 | { longitude: 6.59009, latitude: 46.52296 },
82 | { longitude: 6.59488, latitude: 46.52273 },
83 | { longitude: 6.59814, latitude: 46.52078 },
84 | { longitude: 6.60067, latitude: 46.51869 },
85 | { longitude: 6.60255, latitude: 46.51781 },
86 | { longitude: 6.60289, latitude: 46.51752 },
87 | { longitude: 6.60262, latitude: 46.51694 },
88 | { longitude: 6.60341, latitude: 46.51644 },
89 | { longitude: 6.60626, latitude: 46.51603 },
90 | { longitude: 6.61103, latitude: 46.51458 },
91 | { longitude: 6.61136, latitude: 46.51435 },
92 | { longitude: 6.61177, latitude: 46.51408 },
93 | { longitude: 6.61312, latitude: 46.51317 },
94 | { longitude: 6.61685, latitude: 46.51182 },
95 | { longitude: 6.61708, latitude: 46.51165 },
96 | { longitude: 6.62164, latitude: 46.50887 },
97 | { longitude: 6.62643, latitude: 46.50693 },
98 | ],
99 | };
100 |
--------------------------------------------------------------------------------
/src/stories/Refs.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useMemo, useRef } from 'react';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import { fn } from '@storybook/test';
4 | import './stories.css';
5 |
6 | import Map from '../components/Map';
7 | import {
8 | ColorScheme,
9 | MapType,
10 | Distances,
11 | LoadPriority,
12 | CoordinateRegion,
13 | } from '../util/parameters';
14 |
15 | // @ts-ignore
16 | const token = import.meta.env.STORYBOOK_MAPKIT_JS_TOKEN!;
17 |
18 | const enumArgType = (e: object) => ({
19 | options: Object.values(e).filter((val) => typeof val === 'number'),
20 | control: {
21 | type: 'inline-radio',
22 | labels: Object.values(e).filter((val) => typeof val === 'string'),
23 | },
24 | });
25 | export default {
26 | title: 'MapKit JS refs/Map',
27 | component: Map,
28 | args: {
29 | token,
30 | onLoad: fn(),
31 | onRegionChangeStart: fn(),
32 | onRegionChangeEnd: fn(),
33 | onMapTypeChange: fn(),
34 | onSingleTap: fn(),
35 | onDoubleTap: fn(),
36 | onLongPress: fn(),
37 | onClick: fn(),
38 | onMouseMove: fn(),
39 | onMouseDown: fn(),
40 | onMouseUp: fn(),
41 | onUserLocationChange: fn(),
42 | onUserLocationError: fn(),
43 | },
44 | argTypes: {
45 | colorScheme: enumArgType(ColorScheme),
46 | mapType: enumArgType(MapType),
47 | distances: enumArgType(Distances),
48 | loadPriority: enumArgType(LoadPriority),
49 | },
50 | parameters: {
51 | layout: 'fullscreen',
52 | docs: {
53 | description: {
54 | component:
55 | 'To access methods or properties not exposed by mapkit-react, you can access the `map` object provided by MapKit JS using a reference.',
56 | },
57 | },
58 | },
59 | } as Meta;
60 |
61 | type MapProps = React.ComponentProps;
62 |
63 | export const MapReference: StoryFn = (args) => {
64 | const mapRef = useRef(null);
65 |
66 | // Animate a rotation when the button is pressed
67 | const rotate = useCallback(() => {
68 | if (mapRef.current === null) return;
69 |
70 | if (!mapRef.current.isRotationAvailable) {
71 | // eslint-disable-next-line no-alert
72 | alert('Sorry, rotation is not available.');
73 | return;
74 | }
75 |
76 | mapRef.current.setRotationAnimated(mapRef.current.rotation + 90);
77 | }, [mapRef]);
78 |
79 | return (
80 | <>
81 |
82 |
83 |
84 |
85 | ↻ Rotate
86 |
87 |
88 | >
89 | );
90 | };
91 | MapReference.args = {
92 | paddingBottom: 44,
93 | initialRegion: {
94 | centerLatitude: 37.7812,
95 | centerLongitude: -122.44755,
96 | latitudeDelta: 0.1,
97 | longitudeDelta: 0.11,
98 | },
99 | };
100 |
101 | export const OnLoadEvent = () => {
102 | const mapRef = useRef(null);
103 |
104 | const initialRegion: CoordinateRegion = useMemo(
105 | () => ({
106 | centerLatitude: 48.92630099185955,
107 | centerLongitude: 10.615092941674959,
108 | latitudeDelta: 24.17307048317351,
109 | longitudeDelta: 43.4436668867213,
110 | }),
111 | [],
112 | );
113 |
114 | return (
115 | {
120 | mapRef.current!.setRegionAnimated(
121 | new mapkit.CoordinateRegion(
122 | new mapkit.Coordinate(46.76753351386031, 8.208099002907204),
123 | new mapkit.CoordinateSpan(2.017148245608759, 4.684076997007793),
124 | ),
125 | );
126 | }}
127 | />
128 | );
129 | };
130 | OnLoadEvent.storyName = '`onLoad` Event';
131 |
--------------------------------------------------------------------------------
/src/stories/stories.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | body {
6 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
7 | }
8 |
9 | .form-group {
10 | display: flex;
11 | flex-direction: column;
12 | font-weight: 500;
13 | letter-spacing: -.02em;
14 | }
15 |
16 | .input {
17 | width: 100%;
18 | padding: 12px;
19 | background: white;
20 | border: 1px solid #d2d2d7;
21 | border-radius: 8px;
22 | resize: vertical;
23 | font-family: inherit;
24 | font-size: 16px;
25 | }
26 |
27 | .map-overlay {
28 | position: absolute;
29 | bottom: 0;
30 | left: 0;
31 | width: 100%;
32 | padding: 8px;
33 | }
34 |
35 | .map-overlay-top {
36 | top: 0;
37 | bottom: auto;
38 | }
39 |
40 | .map-overlay button {
41 | display: block;
42 | background-color: white;
43 | width: 100%;
44 | height: 32px;
45 | appearance: none;
46 | border: 0;
47 | border-radius: 8px;
48 |
49 | font-size: 1rem;
50 | font-family: inherit;
51 | font-weight: 500;
52 | letter-spacing: -.02em;
53 | }
54 |
55 | .map-overlay button:active {
56 | background-color: #f1f1f1;
57 | }
58 |
59 | .map-overlay-box {
60 | height: 32px;
61 | border-radius: 8px;
62 | display: flex;
63 | gap: 20px;
64 | align-items: center;
65 | justify-content: center;
66 |
67 | background-color: rgba(255, 255, 255, .6);
68 | backdrop-filter: blur(8px);
69 | -webkit-backdrop-filter: blur(8px);
70 | box-shadow: 0 2px 4px rgba(0, 0, 0, .15);
71 | }
72 |
73 | .map-overlay-box label {
74 | display: flex;
75 | gap: .3em;
76 | }
77 |
78 | .map-overlay-options {
79 | height: auto;
80 | flex-wrap: wrap;
81 | padding: 20px;
82 | }
83 |
84 | .map-overlay-inputs {
85 | display: flex;
86 | flex-wrap: wrap;
87 | gap: 20px;
88 | padding: 20px;
89 | height: auto;
90 | }
91 |
92 | .map-overlay-inputs .form-group {
93 | flex: 1;
94 | min-width: 150px;
95 | }
96 |
97 | .default-annotation-style {
98 | width: 100%;
99 | height: 24px;
100 | background-color: #3b5f82;
101 | border: 1px solid #213549;
102 | color: white;
103 | border-radius: 4px;
104 | text-align: center;
105 | vertical-align: middle;
106 | }
107 |
108 | .landmark {
109 | width: 250px;
110 | padding: 7px 0 0 0;
111 | background: rgba(247, 247, 247, 0.75);
112 | border-radius: 5px;
113 | box-shadow: 10px 10px 50px rgba(0, 0, 0, 0.29);
114 | font-family: Helvetica, Arial, sans-serif;
115 | transform-origin: 0 10px;
116 | }
117 |
118 | .landmark h1 {
119 | margin-top: 0;
120 | padding: 5px 15px;
121 | background: #2aaef5;
122 | color: rgba(255, 255, 255, 0.9);
123 | font-size: 16px;
124 | font-weight: 300;
125 | }
126 |
127 | .landmark section {
128 | padding: 0 15px 5px;
129 | font-size: 14px;
130 | }
131 |
132 | .landmark section p {
133 | margin: 5px 0;
134 | }
135 |
136 | .landmark:after {
137 | content: "";
138 | position: absolute;
139 | top: 7px;
140 | left: -13px;
141 | width: 0;
142 | height: 0;
143 | margin-bottom: -13px;
144 | border-right: 13px solid #2aaef5;
145 | border-top: 13px solid rgba(0, 0, 0, 0);
146 | border-bottom: 13px solid rgba(0, 0, 0, 0);
147 | }
148 |
149 | .custom-annotation-image {
150 | width: 40px;
151 | height: 40px;
152 | }
153 |
154 | .custom-annotation-image img {
155 | width: 100%;
156 | height: 100%;
157 | border-radius: 8px;
158 | }
159 |
160 | .custom-annotation-content {
161 | display: flex;
162 | flex-direction: column;
163 | gap: 5px;
164 | }
165 |
166 | .custom-annotation-content h2 {
167 | font-size: 20px;
168 | font-weight: 600;
169 | margin: 0;
170 | }
171 |
172 | .custom-annotation-content p {
173 | margin: 0;
174 | font-size: 12px;
175 | }
176 |
177 | .custom-annotation-info {
178 | padding: 10px;
179 | }
180 |
181 | /* Animation FROM https: //codepen.io/nelledejones/pen/gOOPWrK */
182 | @keyframes gelatine {
183 |
184 | from,
185 | to {
186 | transform: scale(1, 1);
187 | }
188 |
189 | 25% {
190 | transform: scale(0.9, 1.1);
191 | }
192 |
193 | 50% {
194 | transform: scale(1.1, 0.9);
195 | }
196 |
197 | 75% {
198 | transform: scale(0.95, 1.05);
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/util/forwardMapkitEvent.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | /**
4 | * Forwards a given MapKit JS event to a mapkit-react event.
5 | * @param element The current map | marker | annotation instance
6 | * @param name The name of the MapKit JS event.
7 | * @param handler The event handler of the mapkit-react Map component
8 | * @param eventMap A function that transforms the parameter of the
9 | * MapKit JS event handler to a parameter for the
10 | * mapkit-react event handler.
11 | */
12 | export default function forwardMapkitEvent(
13 | element: mapkit.Map | mapkit.Annotation | mapkit.MarkerAnnotation | null,
14 | name: String,
15 | handler: ((mapkitReactEvent: E) => void) | undefined,
16 | eventMap: (mapkitEvent: any) => E,
17 | ) {
18 | useEffect(() => {
19 | if (!element || !handler) return undefined;
20 |
21 | // @ts-ignore
22 | const mapkitHandler = (e) => {
23 | handler(eventMap(e));
24 | };
25 |
26 | // @ts-ignore
27 | element.addEventListener(name, mapkitHandler);
28 | // @ts-ignore
29 | return () => element.removeEventListener(name, mapkitHandler);
30 | }, [element, handler]);
31 | }
32 |
--------------------------------------------------------------------------------
/src/util/loader.ts:
--------------------------------------------------------------------------------
1 | let loadingPromise: Promise | null = null;
2 |
3 | /**
4 | * Loads the MapKit JS API with the given token.
5 | *
6 | * If the library is already loaded or loading, this function will not attempt
7 | * to load it a second time.
8 | *
9 | * @param token The MapKit JS token
10 | * @returns A promise resolving when the library is loaded.
11 | */
12 | export default function load(token: string): Promise {
13 | if (loadingPromise !== null) {
14 | return loadingPromise;
15 | }
16 |
17 | loadingPromise = new Promise((resolve) => {
18 | const script = document.createElement('script');
19 | script.addEventListener('load', () => {
20 | mapkit.init({
21 | authorizationCallback: (done) => done(token),
22 | });
23 |
24 | resolve();
25 | }, { once: true });
26 | script.src = 'https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js';
27 | script.crossOrigin = 'anonymous';
28 | document.head.appendChild(script);
29 | });
30 | return loadingPromise;
31 | }
32 |
--------------------------------------------------------------------------------
/src/util/parameters.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Color schemes of the map.
3 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/colorschemes}
4 | */
5 | export enum ColorScheme { Light, Dark, Auto }
6 |
7 | /**
8 | * Converts a mapkit-react color scheme value to a MapKit JS color scheme value.
9 | * Must be called after MapKit JS is loaded.
10 | * @param colorScheme The mapkit-react color scheme value (ColorScheme.Dark / ColorScheme.Light)
11 | * or ColorScheme.Auto for dynamic color based on the color preference of the user
12 | * @param isDarkMode The user’s color preference (used for ColorScheme.Auto)
13 | * @returns The MapKit JS color scheme value
14 | */
15 | export function toMapKitColorScheme(colorScheme: ColorScheme, isDarkMode: boolean): string {
16 | switch (colorScheme) {
17 | case ColorScheme.Dark:
18 | return mapkit.Map.ColorSchemes.Dark;
19 | case ColorScheme.Light:
20 | return mapkit.Map.ColorSchemes.Light;
21 | case ColorScheme.Auto:
22 | return isDarkMode
23 | ? mapkit.Map.ColorSchemes.Dark
24 | : mapkit.Map.ColorSchemes.Light;
25 | default:
26 | throw new RangeError('Invalid color scheme');
27 | }
28 | }
29 |
30 | /**
31 | * Types of map to display.
32 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/maptypes}
33 | */
34 | export enum MapType {
35 | /**
36 | * A street map that shows the position of all roads and some road names.
37 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/maptypes/standard}
38 | */
39 | Standard,
40 |
41 | /**
42 | * A street map where your data is emphasized over the underlying map details.
43 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/maptypes/mutedstandard}
44 | */
45 | MutedStandard,
46 |
47 | /**
48 | * A satellite image of the area with road and road name information
49 | * layered on top.
50 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/maptypes/hybrid}
51 | */
52 | Hybrid,
53 |
54 | /**
55 | * A satellite image of the area.
56 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/maptypes/satellite}
57 | */
58 | Satellite,
59 | }
60 |
61 | /**
62 | * Converts a mapkit-react map type value to a MapKit JS map type value.
63 | * Must be called after MapKit JS is loaded.
64 | * @param mapType The mapkit-react map type value
65 | * @returns The MapKit JS map type value
66 | */
67 | export function toMapKitMapType(mapType: MapType): string {
68 | switch (mapType) {
69 | case MapType.Standard:
70 | return mapkit.Map.MapTypes.Standard;
71 | case MapType.MutedStandard:
72 | return mapkit.Map.MapTypes.MutedStandard;
73 | case MapType.Hybrid:
74 | return mapkit.Map.MapTypes.Hybrid;
75 | case MapType.Satellite:
76 | return mapkit.Map.MapTypes.Satellite;
77 | default:
78 | throw new RangeError('Invalid map type');
79 | }
80 | }
81 |
82 | /**
83 | * Converts a MapKit JS region value to a mapkit-react region value.
84 | * @param region The MapKit JS region value
85 | * @returns The mapkit-react region value
86 | */
87 | export function fromMapKitRegion(region: mapkit.CoordinateRegion): CoordinateRegion {
88 | return {
89 | centerLatitude: region.center.latitude,
90 | centerLongitude: region.center.longitude,
91 | latitudeDelta: region.span.latitudeDelta,
92 | longitudeDelta: region.span.longitudeDelta,
93 | };
94 | }
95 |
96 | /**
97 | * Converts a MapKit JS map type value to a mapkit-react map type value.
98 | * Must be called after MapKit JS is loaded.
99 | * @param mapType The MapKit JS map type value
100 | * @returns The mapkit-react map type value
101 | */
102 | export function fromMapKitMapType(mapType: string): MapType {
103 | switch (mapType) {
104 | case mapkit.Map.MapTypes.Standard:
105 | return MapType.Standard;
106 | case mapkit.Map.MapTypes.MutedStandard:
107 | return MapType.MutedStandard;
108 | case mapkit.Map.MapTypes.Hybrid:
109 | return MapType.Hybrid;
110 | case mapkit.Map.MapTypes.Satellite:
111 | return MapType.Satellite;
112 | default:
113 | throw new RangeError('Invalid map type');
114 | }
115 | }
116 |
117 | /**
118 | * System of measurement that displays on the map.
119 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/distances}
120 | */
121 | export enum Distances {
122 | /**
123 | * The measurement system is adaptive, and determined based on the map's language.
124 | */
125 | Adaptive,
126 | Metric,
127 | Imperial,
128 | }
129 |
130 | /**
131 | * Converts a mapkit-react map distances to a MapKit JS map distances.
132 | * Must be called after MapKit JS is loaded.
133 | * @param distances The mapkit-react map distances
134 | * @returns The MapKit JS map distances
135 | */
136 | export function toMapKitDistances(distances: Distances): string {
137 | switch (distances) {
138 | case Distances.Adaptive:
139 | return mapkit.Map.Distances.Adaptive;
140 | case Distances.Metric:
141 | return mapkit.Map.Distances.Metric;
142 | case Distances.Imperial:
143 | return mapkit.Map.Distances.Imperial;
144 | default:
145 | throw new RangeError('Invalid distances value');
146 | }
147 | }
148 |
149 | /**
150 | * A value MapKit JS uses for prioritizing the visibility of specific map features
151 | * before the underlaying map tiles.
152 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapconstructoroptions/4096368-loadpriority}
153 | */
154 | export enum LoadPriority {
155 | /**
156 | * Prioritizes loading of the map land cover and borders, without POIs or labels.
157 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/loadpriorities/landcover}
158 | */
159 | LandCover,
160 |
161 | /**
162 | * Prioritizes loading of the full standard map, with rendered POIs.
163 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/loadpriorities/pointsofinterest}
164 | */
165 | PointsOfInterest,
166 |
167 | /**
168 | * Signifies no preferences over what to prioritize when loading the map.
169 | * @see {@link https://developer.apple.com/documentation/mapkitjs/map/loadpriorities/none}
170 | */
171 | None,
172 | }
173 |
174 | /**
175 | * Converts a mapkit-react load priority to a MapKit JS load priority.
176 | * Must be called after MapKit JS is loaded.
177 | * @param mapPriority The mapkit-react load priority
178 | * @returns The MapKit JS load priority
179 | */
180 | export function toMapKitLoadPriority(loadPriority: LoadPriority): string | null {
181 | switch (loadPriority) {
182 | case LoadPriority.LandCover:
183 | // @ts-ignore
184 | return mapkit.Map.LoadPriorities.LandCover;
185 | case LoadPriority.PointsOfInterest:
186 | // @ts-ignore
187 | return mapkit.Map.LoadPriorities.PointsOfInterest;
188 | case LoadPriority.None:
189 | // @ts-ignore
190 | return mapkit.Map.LoadPriorities.None;
191 | default:
192 | throw new RangeError('Invalid load priority');
193 | }
194 | }
195 |
196 | /**
197 | * A value MapKit JS uses for prioritizing the visibility of specific annotations.
198 | * @see {@link https://developer.apple.com/documentation/mapkitjs/annotationconstructoroptions/2991165-displaypriority}
199 | */
200 | export enum DisplayPriority {
201 | Low = 'low',
202 | High = 'high',
203 | Required = 'required',
204 | }
205 |
206 | /**
207 | * Converts a mapkit-react display priority to a MapKit JS display priority.
208 | * Must be called after MapKit JS is loaded.
209 | * @param displayPriority The mapkit-react display priority or a number 0 to 1000
210 | * @returns The MapKit JS display priority
211 | */
212 | export function toMapKitDisplayPriority(displayPriority: DisplayPriority | number): number | null {
213 | if (typeof displayPriority === 'number') {
214 | if (displayPriority < 0 || displayPriority > 1000) {
215 | throw new RangeError('Display priority is out of range (0 to 1000)');
216 | } else {
217 | return displayPriority;
218 | }
219 | }
220 |
221 | switch (displayPriority) {
222 | case DisplayPriority.Low:
223 | // @ts-ignore
224 | return mapkit.Annotation.DisplayPriority.Low;
225 | case DisplayPriority.High:
226 | // @ts-ignore
227 | return mapkit.Annotation.DisplayPriority.High;
228 | case DisplayPriority.Required:
229 | // @ts-ignore
230 | return mapkit.Annotation.DisplayPriority.Required;
231 | default:
232 | throw new RangeError('Invalid display priority');
233 | }
234 | }
235 |
236 | /**
237 | * Constants indicating the visibility of different adaptive map features.
238 | * @see {@link https://developer.apple.com/documentation/mapkitjs/featurevisibility}
239 | */
240 | export enum FeatureVisibility {
241 | /**
242 | * A constant indicating that the feature is always hidden.
243 | * @see {@link https://developer.apple.com/documentation/mapkitjs/featurevisibility/hidden}
244 | */
245 | Hidden,
246 |
247 | /**
248 | * A constant indicating that the feature is always visible.
249 | * @see {@link https://developer.apple.com/documentation/mapkitjs/featurevisibility/visible}
250 | */
251 | Visible,
252 |
253 | /**
254 | * A constant indicating that feature visibility adapts to the current map state.
255 | * @see {@link https://developer.apple.com/documentation/mapkitjs/featurevisibility/adaptive}
256 | */
257 | Adaptive,
258 | }
259 |
260 | /**
261 | * Converts a mapkit-react visibility to a MapKit JS visibility.
262 | * Must be called after MapKit JS is loaded.
263 | * @param featureVisibility The mapkit-react visibility
264 | * @returns The MapKit JS visibility
265 | */
266 | export function toMapKitFeatureVisibility(featureVisibility: FeatureVisibility): string {
267 | switch (featureVisibility) {
268 | case FeatureVisibility.Adaptive:
269 | return mapkit.FeatureVisibility.Adaptive;
270 | case FeatureVisibility.Visible:
271 | return mapkit.FeatureVisibility.Visible;
272 | case FeatureVisibility.Hidden:
273 | return mapkit.FeatureVisibility.Hidden;
274 | default:
275 | throw new RangeError('Invalid feature visibility');
276 | }
277 | }
278 |
279 | /**
280 | * Point-of-interest categories.
281 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/pointofinterestcategory}
282 | */
283 | export enum PointOfInterestCategory {
284 | Airport, AmusementPark, Aquarium, ATM, Bakery, Bank, Beach, Brewery, Cafe,
285 | Campground, CarRental, EVCharger, FireStation, FitnessCenter, FoodMarket,
286 | GasStation, Hospital, Hotel, Laundry, Library, Marina, MovieTheater, Museum,
287 | NationalPark, Nightlife, Park, Parking, Pharmacy, Police, PostOffice,
288 | PublicTransport, Restaurant, Restroom, School, Stadium, Store, Theater,
289 | University, Winery, Zoo,
290 | }
291 |
292 | /**
293 | * Converts a mapkit-react POI category value to a MapKit JS POI category value.
294 | * Must be called after MapKit JS is loaded.
295 | * @param category The mapkit-react point of interest category value
296 | * @returns The MapKit JS point of interest category value
297 | */
298 | export function toMapKitPOICategory(category: PointOfInterestCategory):
299 | mapkit.PointOfInterestCategory {
300 | switch (category) {
301 | case PointOfInterestCategory.Airport:
302 | return mapkit.PointOfInterestCategory.Airport;
303 | case PointOfInterestCategory.AmusementPark:
304 | return mapkit.PointOfInterestCategory.AmusementPark;
305 | case PointOfInterestCategory.Aquarium:
306 | return mapkit.PointOfInterestCategory.Aquarium;
307 | case PointOfInterestCategory.ATM:
308 | return mapkit.PointOfInterestCategory.ATM;
309 | case PointOfInterestCategory.Bakery:
310 | return mapkit.PointOfInterestCategory.Bakery;
311 | case PointOfInterestCategory.Bank:
312 | return mapkit.PointOfInterestCategory.Bank;
313 | case PointOfInterestCategory.Beach:
314 | return mapkit.PointOfInterestCategory.Beach;
315 | case PointOfInterestCategory.Brewery:
316 | return mapkit.PointOfInterestCategory.Brewery;
317 | case PointOfInterestCategory.Cafe:
318 | return mapkit.PointOfInterestCategory.Cafe;
319 | case PointOfInterestCategory.Campground:
320 | return mapkit.PointOfInterestCategory.Campground;
321 | case PointOfInterestCategory.CarRental:
322 | return mapkit.PointOfInterestCategory.CarRental;
323 | case PointOfInterestCategory.EVCharger:
324 | return mapkit.PointOfInterestCategory.EVCharger;
325 | case PointOfInterestCategory.FireStation:
326 | return mapkit.PointOfInterestCategory.FireStation;
327 | case PointOfInterestCategory.FitnessCenter:
328 | return mapkit.PointOfInterestCategory.FitnessCenter;
329 | case PointOfInterestCategory.FoodMarket:
330 | return mapkit.PointOfInterestCategory.FoodMarket;
331 | case PointOfInterestCategory.GasStation:
332 | return mapkit.PointOfInterestCategory.GasStation;
333 | case PointOfInterestCategory.Hospital:
334 | return mapkit.PointOfInterestCategory.Hospital;
335 | case PointOfInterestCategory.Hotel:
336 | return mapkit.PointOfInterestCategory.Hotel;
337 | case PointOfInterestCategory.Laundry:
338 | return mapkit.PointOfInterestCategory.Laundry;
339 | case PointOfInterestCategory.Library:
340 | return mapkit.PointOfInterestCategory.Library;
341 | case PointOfInterestCategory.Marina:
342 | return mapkit.PointOfInterestCategory.Marina;
343 | case PointOfInterestCategory.MovieTheater:
344 | return mapkit.PointOfInterestCategory.MovieTheater;
345 | case PointOfInterestCategory.Museum:
346 | return mapkit.PointOfInterestCategory.Museum;
347 | case PointOfInterestCategory.NationalPark:
348 | return mapkit.PointOfInterestCategory.NationalPark;
349 | case PointOfInterestCategory.Nightlife:
350 | return mapkit.PointOfInterestCategory.Nightlife;
351 | case PointOfInterestCategory.Park:
352 | return mapkit.PointOfInterestCategory.Park;
353 | case PointOfInterestCategory.Parking:
354 | return mapkit.PointOfInterestCategory.Parking;
355 | case PointOfInterestCategory.Pharmacy:
356 | return mapkit.PointOfInterestCategory.Pharmacy;
357 | case PointOfInterestCategory.Police:
358 | return mapkit.PointOfInterestCategory.Police;
359 | case PointOfInterestCategory.PostOffice:
360 | return mapkit.PointOfInterestCategory.PostOffice;
361 | case PointOfInterestCategory.PublicTransport:
362 | return mapkit.PointOfInterestCategory.PublicTransport;
363 | case PointOfInterestCategory.Restaurant:
364 | return mapkit.PointOfInterestCategory.Restaurant;
365 | case PointOfInterestCategory.Restroom:
366 | return mapkit.PointOfInterestCategory.Restroom;
367 | case PointOfInterestCategory.School:
368 | return mapkit.PointOfInterestCategory.School;
369 | case PointOfInterestCategory.Stadium:
370 | return mapkit.PointOfInterestCategory.Stadium;
371 | case PointOfInterestCategory.Store:
372 | return mapkit.PointOfInterestCategory.Store;
373 | case PointOfInterestCategory.Theater:
374 | return mapkit.PointOfInterestCategory.Theater;
375 | case PointOfInterestCategory.University:
376 | return mapkit.PointOfInterestCategory.University;
377 | case PointOfInterestCategory.Winery:
378 | return mapkit.PointOfInterestCategory.Winery;
379 | case PointOfInterestCategory.Zoo:
380 | return mapkit.PointOfInterestCategory.Zoo;
381 | default:
382 | throw new RangeError('Invalid point of interest category');
383 | }
384 | }
385 |
386 | /**
387 | * A point on the Earth’s surface.
388 | */
389 | export interface Coordinate {
390 | /**
391 | * The latitude in degrees.
392 | */
393 | latitude: number;
394 |
395 | /**
396 | * The longitude in degrees.
397 | */
398 | longitude: number;
399 | }
400 |
401 | /**
402 | * A rectangular geographic region that centers around a latitude and longitude coordinate.
403 | * @see {@link https://developer.apple.com/documentation/mapkitjs/mapkit/coordinateregion/2973861-mapkit_coordinateregion}
404 | */
405 | export interface CoordinateRegion {
406 | /**
407 | * The latitude of the center point in degrees.
408 | */
409 | centerLatitude: number;
410 |
411 | /**
412 | * The longitude of the center point in degrees.
413 | */
414 | centerLongitude: number;
415 |
416 | /**
417 | * The amount of north-to-south distance (in degrees) to display for the map
418 | * region. Unlike longitudinal distances, which vary based on the latitude,
419 | * one degree of latitude is always approximately 111 km (69 mi.).
420 | */
421 | latitudeDelta: number;
422 |
423 | /**
424 | * The amount of east-to-west distance (in degrees) to display for the map
425 | * region. The number of kilometers (or miles) that a longitude range spans
426 | * varies based on the latitude. For example, one degree of longitude spans
427 | * a distance of approximately 111 km (69 miles mi.) at the equator,
428 | * approximately 88 km (or 55mi.) at 37º north latitude (the latitude of
429 | * San Francisco), and shrinks to 0 km (0 mi.) at the poles.
430 | */
431 | longitudeDelta: number;
432 | }
433 |
434 | /**
435 | * Converts a mapkit-react coordinate region to a MapKit JS coordinate region.
436 | * Must be called after MapKit JS is loaded.
437 | * @param region The mapkit-react coordinate region
438 | * @returns The MapKit JS coordinate region
439 | */
440 | export function toMapKitCoordinateRegion(region: CoordinateRegion) : mapkit.CoordinateRegion {
441 | return new mapkit.CoordinateRegion(
442 | new mapkit.Coordinate(region.centerLatitude, region.centerLongitude),
443 | new mapkit.CoordinateSpan(region.latitudeDelta, region.longitudeDelta),
444 | );
445 | }
446 |
--------------------------------------------------------------------------------
/support.md:
--------------------------------------------------------------------------------
1 | # Supported MapKit JS features
2 |
3 | ## `mapkit`
4 |
5 | ### Constructor parameters
6 |
7 | | Feature | Supported |
8 | | --------------------------------------- | ------------------------------------------------------------------------------------- |
9 | | MapKitInitOptions.language | ❌ |
10 | | MapKitInitOptions.authorizationCallback | ⚠️You can only pass token as strings, callbacks are not supported. |
11 |
12 | ### Properties
13 |
14 | | Feature | Supported |
15 | | ---------------- | --------- |
16 | | Librairies | ❌ |
17 | | loadedLibrairies | ❌ |
18 | | maps | ❌ |
19 |
20 | ### Methods
21 |
22 | | Feature | Supported |
23 | | ------- | --------- |
24 | | load | ❌ |
25 |
26 | ### Events
27 |
28 | | Feature | Supported |
29 | | -------------------- | --------- |
30 | | configuration-change | ❌ |
31 | | error | ❌ |
32 |
33 | ## `mapkit.Map`
34 |
35 | ### Constructor parameters
36 |
37 | #### Visible portion of the map
38 |
39 | | Feature | Supported |
40 | | ------------------------------------ | ------------------ |
41 | | MapConstructorOptions.visibleMapRect | ❌ |
42 | | MapConstructorOptions.region | ✅ (initialRegion) |
43 | | MapConstructorOptions.center | ❌ |
44 | | MapConstructorOptions.rotation | ❌ |
45 | | MapConstructorOptions.tintColor | ❌ |
46 |
47 | #### Appearance and controls
48 |
49 | | Feature | Supported |
50 | | ----------------------------------------- | --------- |
51 | | MapConstructorOptions.colorScheme | ✅ |
52 | | MapConstructorOptions.mapType | ✅ |
53 | | MapConstructorOptions.padding | ✅ |
54 | | MapConstructorOptions.showsMapTypeControl | ✅ |
55 | | MapConstructorOptions.isRotationEnabled | ✅ |
56 | | MapConstructorOptions.showsCompass | ✅ |
57 | | MapConstructorOptions.isZoomEnabled | ✅ |
58 | | MapConstructorOptions.showsZoomControl | ✅ |
59 | | MapConstructorOptions.isScrollEnabled | ✅ |
60 | | MapConstructorOptions.showsScale | ✅ |
61 |
62 | #### Annotations
63 |
64 | | Feature | Supported |
65 | | ------------------------------------------ | -------------------------------------- |
66 | | MapConstructorOptions.annotationForCluster | ❌ |
67 | | MapConstructorOptions.annotations | ✅ |
68 | | MapConstructorOptions.selectedAnnotation | ✅ (use `selected` on the annotations) |
69 |
70 | #### Overlays
71 |
72 | | Feature | Supported |
73 | | ------------------------------------------- | ----------------------------------- |
74 | | MapConstructorOptions.overlays | ✅ |
75 | | MapConstructorOptions.selectedOverlay | ✅ (use `selected` on the overlays) |
76 | | MapConstructorOptions.showsPointsOfInterest | ✅ |
77 | | MapConstructorOptions.pointOfInterestFilter | ✅ |
78 |
79 | #### User location
80 |
81 | | Feature | Supported |
82 | | ---------------------------------------------- | --------- |
83 | | MapConstructorOptions.showsUserLocation | ✅ |
84 | | MapConstructorOptions.tracksUserLocation | ✅ |
85 | | MapConstructorOptions.showsUserLocationControl | ✅ |
86 |
87 | #### Loading priority
88 |
89 | | Feature | Supported |
90 | | ---------------------------------- | --------- |
91 | | MapConstructorOptions.loadPriority | ✅ |
92 |
93 | ### Properties
94 |
95 | #### Interaction properties
96 |
97 | | Feature | Supported |
98 | | ------------------- | --------- |
99 | | isRotationAvailable | ❌ |
100 | | isRotationEnabled | ✅ |
101 | | isScrollEnabled | ✅ |
102 | | isZoomEnabled | ✅ |
103 |
104 | #### Visible portion of the map
105 |
106 | | Feature | Supported |
107 | | --------------- | ---------------------------------------- |
108 | | center | ❌ |
109 | | region | ❌ |
110 | | rotation | ❌ |
111 | | visibleMapRect | ❌ |
112 | | cameraBoundary | ✅ |
113 | | cameraDistance | ❌ |
114 | | cameraZoomRange | ✅ (minCameraDistance/maxCameraDistance) |
115 |
116 | #### Controls
117 |
118 | | Feature | Supported |
119 | | ------------------------ | --------- |
120 | | showsCompass | ✅ |
121 | | showsMapTypeControl | ✅ |
122 | | showsScale | ✅ |
123 | | showsUserLocationControl | ✅ |
124 | | showsZoomControl | ✅ |
125 |
126 | #### Appearance
127 |
128 | | Feature | Supported |
129 | | --------------------- | --------- |
130 | | colorScheme | ✅ |
131 | | distances | ✅ |
132 | | mapType | ✅ |
133 | | padding | ✅ |
134 | | pointOfInterestFilter | ✅ |
135 | | showsPointsOfInterest | ✅ |
136 | | tintColor | ❌ |
137 |
138 | #### Annotations
139 |
140 | | Feature | Supported |
141 | | ------------------ | --------- |
142 | | annotations | ✅ |
143 | | selectedAnnotation | ❌ |
144 |
145 | #### Overlays
146 |
147 | | Feature | Supported |
148 | | --------------- | ----------------------------------- |
149 | | overlays | ✅ |
150 | | selectedOverlay | ✅ (use `selected` on the overlays) |
151 | | tileOverlays | ❌ |
152 |
153 | #### User location
154 |
155 | | Feature | Supported |
156 | | ---------------------- | --------- |
157 | | showsUserLocation | ✅ |
158 | | tracksUserLocation | ✅ |
159 | | userLocationAnnotation | ❌ |
160 |
161 | #### Access the element
162 |
163 | | Feature | Supported |
164 | | ------- | --------- |
165 | | element | ❌ |
166 |
167 | #### Selectable map features
168 |
169 | | Feature | Supported |
170 | | --------------------- | --------- |
171 | | selectableMapFeatures | ❌ |
172 |
173 | #### Feature loading prioritization
174 |
175 | | Feature | Supported |
176 | | ------------ | --------- |
177 | | loadPriority | ✅ |
178 |
179 | ### Events
180 |
181 | It is possible to indirectly listen to the events by using the reference to `mapkit.Map` exposed by the `Map` component.
182 |
183 | #### Respond to map display events
184 |
185 | | Feature | Supported |
186 | | ------------------- | --------- |
187 | | region-change-start | ✅ |
188 | | region-change-end | ✅ |
189 | | rotation-start | ❌ |
190 | | rotation-end | ❌ |
191 | | scroll-start | ❌ |
192 | | scroll-end | ❌ |
193 | | zoom-start | ❌ |
194 | | zoom-end | ❌ |
195 | | map-type-change | ✅ |
196 |
197 | #### Respond to user location events
198 |
199 | | Feature | Supported |
200 | | -------------------- | --------- |
201 | | user-location-change | ✅ |
202 | | user-location-error | ✅ |
203 |
204 | #### Respond to map interaction events
205 |
206 | | Feature | Supported |
207 | | ---------- | --------- |
208 | | single-tap | ✅ |
209 | | double-tap | ✅ |
210 | | long-press | ✅ |
211 |
212 | ### Methods
213 |
214 | To call methods on the `mapkit.Map` object, you can use the reference exposed by the `Map` component.
215 |
216 | ## `mapkit.Annotation`
217 |
218 | ### Constructor parameters
219 |
220 | | Feature | Supported |
221 | | ------------------------------------------------- | --------- |
222 | | AnnotationConstructorOptions.title | ✅ |
223 | | AnnotationConstructorOptions.subtitle | ✅ |
224 | | AnnotationConstructorOptions.accessibilityLabel | ✅ |
225 | | AnnotationConstructorOptions.data | ❌ |
226 | | AnnotationConstructorOptions.draggable | ✅ |
227 | | AnnotationConstructorOptions.visible | ✅ |
228 | | AnnotationConstructorOptions.enabled | ✅ |
229 | | AnnotationConstructorOptions.selected | ✅ |
230 | | AnnotationConstructorOptions.calloutEnabled | ✅ |
231 | | AnnotationConstructorOptions.animates | ✅ |
232 | | AnnotationConstructorOptions.appearanceAnimation | ✅ |
233 | | AnnotationConstructorOptions.anchorOffset | ✅ |
234 | | AnnotationConstructorOptions.calloutOffset | ✅ |
235 | | AnnotationConstructorOptions.callout | ✅ |
236 | | AnnotationConstructorOptions.size | ✅ |
237 | | AnnotationConstructorOptions.displayPriority | ✅ |
238 | | AnnotationConstructorOptions.collisionMode | ✅ |
239 | | AnnotationConstructorOptions.padding | ✅ |
240 | | AnnotationConstructorOptions.clusteringIdentifier | ✅ |
241 |
242 | ### Properties
243 |
244 | #### Getting the map and element
245 |
246 | | Feature | Supported |
247 | | ------- | --------- |
248 | | map | ❌ |
249 | | element | ❌ |
250 |
251 | #### Data, titles, and accessibility label
252 |
253 | | Feature | Supported |
254 | | ------------------ | --------- |
255 | | data | ✅ |
256 | | title | ✅ |
257 | | subtitle | ✅ |
258 | | accessibilityLabel | ✅ |
259 |
260 | #### Appearance
261 |
262 | | Feature | Supported |
263 | | ------------------- | --------- |
264 | | coordinate | ✅ |
265 | | anchorOffset | ✅ |
266 | | appearanceAnimation | ✅ |
267 | | displayPriority | ✅ |
268 | | padding | ✅ |
269 | | size | ✅ |
270 | | visible | ✅ |
271 |
272 | #### Interaction behavior
273 |
274 | | Feature | Supported |
275 | | --------- | --------- |
276 | | animates | ✅ |
277 | | draggable | ✅ |
278 | | selected | ✅ |
279 | | enabled | ✅ |
280 |
281 | #### Callouts
282 |
283 | | Feature | Supported |
284 | | -------------- | --------- |
285 | | callout | ✅ |
286 | | calloutEnabled | ✅ |
287 | | calloutOffset | ✅ |
288 |
289 | #### Clustering
290 |
291 | | Feature | Supported |
292 | | -------------------- | --------- |
293 | | memberAnnotations | ❌ |
294 | | clusteringIdentifier | ✅ |
295 | | collisionMode | ✅ |
296 |
297 | ### Events
298 |
299 | | Feature | Supported |
300 | | ---------- | --------- |
301 | | select | ✅ |
302 | | deselect | ✅ |
303 | | drag-start | ✅ |
304 | | dragging | ✅ |
305 | | drag-end | ✅ |
306 |
307 | ## `mapkit.ImageAnnotation`
308 |
309 | _❌ Not currently supported by mapkit-react._
310 |
311 | ## `mapkit.MarkerAnnotation`
312 |
313 | ### Constructor parameters
314 |
315 | | Feature | Supported |
316 | | ------------------------------------------------------- | --------- |
317 | | MarkerAnnotationConstructorOptions.titleVisibility | ✅ |
318 | | MarkerAnnotationConstructorOptions.subtitleVisibility | ✅ |
319 | | MarkerAnnotationConstructorOptions.color | ✅ |
320 | | MarkerAnnotationConstructorOptions.glyphColor | ✅ |
321 | | MarkerAnnotationConstructorOptions.glyphText | ✅ |
322 | | MarkerAnnotationConstructorOptions.glyphImage | ✅ |
323 | | MarkerAnnotationConstructorOptions.selectedGlyphImage | ✅ |
324 | | MarkerAnnotationConstructorOptions.title | ✅ |
325 | | MarkerAnnotationConstructorOptions.subtitle | ✅ |
326 | | MarkerAnnotationConstructorOptions.accessibilityLabel | ✅ |
327 | | MarkerAnnotationConstructorOptions.data | ❌ |
328 | | MarkerAnnotationConstructorOptions.draggable | ✅ |
329 | | MarkerAnnotationConstructorOptions.visible | ✅ |
330 | | MarkerAnnotationConstructorOptions.enabled | ✅ |
331 | | MarkerAnnotationConstructorOptions.selected | ✅ |
332 | | MarkerAnnotationConstructorOptions.calloutEnabled | ✅ |
333 | | MarkerAnnotationConstructorOptions.animates | ✅ |
334 | | MarkerAnnotationConstructorOptions.appearanceAnimation | ✅ |
335 | | MarkerAnnotationConstructorOptions.anchorOffset | ✅ |
336 | | MarkerAnnotationConstructorOptions.calloutOffset | ✅ |
337 | | MarkerAnnotationConstructorOptions.callout | ✅ |
338 | | MarkerAnnotationConstructorOptions.size | ❌ |
339 | | MarkerAnnotationConstructorOptions.displayPriority | ✅ |
340 | | MarkerAnnotationConstructorOptions.collisionMode | ✅ |
341 | | MarkerAnnotationConstructorOptions.padding | ✅ |
342 | | MarkerAnnotationConstructorOptions.clusteringIdentifier | ✅ |
343 |
344 | ### Properties
345 |
346 | #### Visibility
347 |
348 | | Feature | Supported |
349 | | ------------------ | --------- |
350 | | subtitleVisibility | ✅ |
351 | | titleVisibility | ✅ |
352 |
353 | #### Appearance
354 |
355 | | Feature | Supported |
356 | | ---------- | --------- |
357 | | color | ✅ |
358 | | glyphColor | ✅ |
359 |
360 | #### Glyph image and text
361 |
362 | | Feature | Supported |
363 | | ------------------ | --------- |
364 | | glyphText | ✅ |
365 | | glyphImage | ✅ |
366 | | selectedGlyphImage | ✅ |
367 |
368 | ### Events
369 |
370 | | Feature | Supported |
371 | | ---------- | --------- |
372 | | select | ✅ |
373 | | deselect | ✅ |
374 | | drag-start | ✅ |
375 | | dragging | ✅ |
376 | | drag-end | ✅ |
377 |
378 | ## `mapkit.Geocoder`
379 |
380 | _❌ Not currently supported by mapkit-react._
381 |
382 | ## `mapkit.Search`
383 |
384 | _❌ Not currently supported by mapkit-react._
385 |
386 | ## `mapkit.PointOfInterestSearch`
387 |
388 | _❌ Not currently supported by mapkit-react._
389 |
390 | ## `mapkit.MapFeatureAnnotation`
391 |
392 | _❌ Not currently supported by mapkit-react._
393 |
394 | ## `mapkit.MapFeatureAnnotationGlyphImage`
395 |
396 | _❌ Not currently supported by mapkit-react._
397 |
398 | ## `mapkit.Directions`
399 |
400 | _❌ Not currently supported by mapkit-react._
401 |
402 | ## `mapkit.ItemCollection`
403 |
404 | _❌ Not currently supported by mapkit-react._
405 |
406 | ## `mapkit.CircleOverlay`
407 |
408 | _❌ Not currently supported by mapkit-react._
409 |
410 | ## `mapkit.PolylineOverlay`
411 |
412 | ### Properties
413 |
414 | | Feature | Supported |
415 | | -------------------- | --------- |
416 | | points | ✅ |
417 | | data | ❌ |
418 | | visible | ✅ |
419 | | enabled | ✅ |
420 | | selected | ✅ |
421 | | style.lineCap | ✅ |
422 | | style.lineDash | ✅ |
423 | | style.lineDashOffset | ✅ |
424 | | style.lineJoin | ✅ |
425 | | style.lineWidth | ✅ |
426 | | style.lineGradient | ❌ |
427 | | style.strokeColor | ✅ |
428 | | style.strokeOpacity | ✅ |
429 | | style.strokeStart | ✅ |
430 | | style.strokeEnd | ✅ |
431 | | map | ❌ |
432 |
433 | ### Events
434 |
435 | | Feature | Supported |
436 | | -------- | --------- |
437 | | select | ✅ |
438 | | deselect | ✅ |
439 |
440 | ## `mapkit.PolygonOverlay`
441 |
442 | ### Properties
443 |
444 | | Feature | Supported |
445 | | -------------------- | --------- |
446 | | points | ✅ |
447 | | data | ❌ |
448 | | visible | ✅ |
449 | | enabled | ✅ |
450 | | selected | ✅ |
451 | | style.lineDash | ✅ |
452 | | style.lineDashOffset | ✅ |
453 | | style.lineJoin | ✅ |
454 | | style.lineWidth | ✅ |
455 | | style.lineGradient | ❌ |
456 | | style.strokeColor | ✅ |
457 | | style.strokeOpacity | ✅ |
458 | | style.strokeStart | ✅ |
459 | | style.strokeEnd | ✅ |
460 | | style.fillColor | ✅ |
461 | | style.fillOpacity | ✅ |
462 | | style.fillRule | ✅ |
463 | | map | ❌ |
464 |
465 | ### Events
466 |
467 | | Feature | Supported |
468 | | -------- | --------- |
469 | | select | ✅ |
470 | | deselect | ✅ |
471 |
472 | ## `mapkit.TileOverlay`
473 |
474 | _❌ Not currently supported by mapkit-react._
475 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | "jsx": "react", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26 |
27 | /* Modules */
28 | "module": "ESNext", /* Specify what module code is generated. */
29 | // "rootDir": "./", /* Specify the root folder within your source files. */
30 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38 | // "resolveJsonModule": true, /* Enable importing .json files. */
39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
40 |
41 | /* JavaScript Support */
42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
45 |
46 | /* Emit */
47 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
49 | "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
50 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */
51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
52 | "outDir": "dist", /* Specify an output folder for all emitted files. */
53 | // "removeComments": true, /* Disable emitting comments. */
54 | // "noEmit": true, /* Disable emitting files from a compilation. */
55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
63 | // "newLine": "crlf", /* Set the newline character for emitting files. */
64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
68 | "declarationDir": "types", /* Specify the output directory for generated declaration files. */
69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
70 |
71 | /* Interop Constraints */
72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
73 | "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
77 |
78 | /* Type Checking */
79 | "strict": true, /* Enable all strict type-checking options. */
80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
98 |
99 | /* Completeness */
100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import dts from 'vite-plugin-dts';
3 | import react from '@vitejs/plugin-react';
4 | import * as path from 'path';
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [
9 | dts(),
10 | react(),
11 | ],
12 | build: {
13 | lib: {
14 | entry: path.resolve(__dirname, 'src/index.ts'),
15 | name: 'mapkit-react',
16 | fileName: 'mapkit-react',
17 | },
18 | rollupOptions: {
19 | external: ['react', 'react/jsx-runtime', 'react-dom', 'react-dom/client'],
20 | output: {
21 | globals: {
22 | react: 'React',
23 | },
24 | },
25 | },
26 | },
27 | });
28 |
--------------------------------------------------------------------------------