├── .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 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |

Nicolas Ettlin

💻 📖 💡 👀

Derek Reynolds

💻 📖 💡

Vladyslav Parashchenko

💻

Gabriel Hall

💻

Tim Nikischin

💻

Jesse Klotz

💻
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 |
176 |

{subtitle ?? ''}

177 |

178 | 179 | Website 180 | 181 |

182 |
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 | 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 | 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 | 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 | 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 |
138 |
139 | 149 | 159 |
160 |
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 |
227 |

{subtitle ?? ''}

228 |

229 | 230 | Website 231 | 232 |

233 |
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 | 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 | --------------------------------------------------------------------------------