diff --git a/docs/api-reference/components/map.md b/docs/api-reference/components/map.md index 9253b40f..201f531c 100644 --- a/docs/api-reference/components/map.md +++ b/docs/api-reference/components/map.md @@ -83,20 +83,23 @@ If your application renders the map on a subpage or otherwise mounts and unmounts the `Map` component a lot, this can cause new map instances to be created with every mount of the component. Since the pricing of the Google Maps JavaScript API is based on map-views (effectively calls to the -`google.maps.Map` constructor), this can quickly become a problem. +`google.maps.Map` constructor), this can quickly cause a problem. The `Map` component can be configured to re-use already created maps with -the `reuseMaps` prop. When enabled, all `Map` components created with the same -`mapId` will reuse previously created instances instead of creating new ones. +the [`reuseMaps`](#reusemaps-boolean) prop. +When enabled, all `Map` components created with the same [`mapId`](#mapid-string), +[`colorScheme`](#colorscheme-googlemapscolorschemegmp-color-scheme-type) and +[`renderingType`](#renderingtype-googlemapsrenderingtypegmp-rendering-type) will reuse +previously created instances instead of creating new ones. :::warning -In the 1.0.0 version, support for map-caching is still a bit experimental, and +In the current version, support for map-caching is still a bit experimental, and there are some known issues when maps are being reused with different sets of options applied. In most simpler situations, like when showing the very same component multiple times, it should work fine. -If you experience any problems using this feature, please file a +If you experience any problems using this feature, please file a [bug-report][rgm-new-issue] or [start a discussion][rgm-discussions] on GitHub. ::: @@ -131,6 +134,19 @@ to be provided in some way for the map to render. This can be done ### General Props +:::info + +The Maps JavaScript API doesn't allow changing the Map ID, the color scheme +or the rendering-type of a map after it has been created. This isn't the +case for this component. However, the internal `google.maps.Map` instance +has to be recreated when the `mapId`, `colorScheme` or `renderingType` props +are changed, **which will cause additional cost**. + +See the [`reuseMaps`](#reusemaps-boolean) prop if your application has to +dynamically switch between multiple Map IDs, color schemes or rendering types. + +::: + #### `id`: string A string that identifies the map component. This is required when multiple @@ -139,20 +155,33 @@ maps are present in the same APIProvider context to be able to access them using #### `mapId`: string -The [Map ID][gmp-mapid] of the map. +The [Map ID][gmp-mapid] of the map. This is required if you want to make use +of the [Cloud-based maps styling][gmp-map-styling]. -:::info +#### `colorScheme`: [google.maps.ColorScheme][gmp-color-scheme-type] + +The [color-scheme][gmp-color-scheme] to be used by the map. Can be +`'LIGHT'`, `'DARK'`, `'FOLLOW_SYSTEM'` or one of the +`ColorScheme` constants +(`import {ColorScheme} from '@vis.gl/react-google-maps';`). -The Maps JavaScript API doesn't allow changing the Map ID of a map after it -has been created. This isn't the case for this component. However, the -internal `google.maps.Map` instance has to be recreated when the `mapId` prop -changes, which might cause additional cost. +:::note -See the [reuseMaps](#reusemaps-boolean) parameter if your application has to -repeatedly switch between multiple Map IDs. +Custom styles that use Map IDs only apply to the light color scheme for roadmap map types. ::: +#### `renderingType`: [google.maps.RenderingType][gmp-rendering-type] + +The desired rendering type the renderer should use. Can be `'RASTER'` or +`'VECTOR'` or one of the `RenderingType` constants +(`import {RenderingType} from '@vis.gl/react-google-maps';`). + +If not set, the cloud configuration for the map ID will determine the +rendering type (if available). Please note that vector maps may not be +available for all devices and browsers, and the map will fall back to a +raster map as needed. + #### `style`: React.CSSProperties Additional style rules to apply to the map dom-element. By default, this will @@ -166,8 +195,9 @@ style-prop is no longer applied. #### `reuseMaps`: boolean -Enable map-instance caching for this component. When caching is enabled, -this component will reuse map instances created with the same `mapId`. +Enable map-instance caching for this component. When caching is enabled, +this component will reuse map instances created with the same `mapId`, +`colorScheme` and `renderingType`. See also the section [Map Instance Caching](#map-instance-caching) above. @@ -333,7 +363,11 @@ to get access to the `google.maps.Map` object rendered in the `` component. [gmp-pad]: https://developers.google.com/maps/documentation/javascript/reference/coordinates#Padding [gmp-ll]: https://developers.google.com/maps/documentation/javascript/reference/coordinates#LatLngLiteral [gmp-coordinates]: https://developers.google.com/maps/documentation/javascript/coordinates +[gmp-color-scheme]: https://developers.google.com/maps/documentation/javascript/mapcolorscheme +[gmp-color-scheme-type]: https://developers.google.com/maps/documentation/javascript/reference/map#ColorScheme [gmp-mapid]: https://developers.google.com/maps/documentation/get-map-id +[gmp-map-styling]: https://developers.google.com/maps/documentation/javascript/cloud-customization +[gmp-rendering-type]: https://developers.google.com/maps/documentation/javascript/reference/map#RenderingType [api-provider]: ./api-provider.md [get-max-tilt]: https://github.com/visgl/react-google-maps/blob/4319bd3b68c40b9aa9b0ce7f377b52d20e824849/src/libraries/limit-tilt-range.ts#L4-L19 [map-source]: https://github.com/visgl/react-google-maps/tree/main/src/components/map diff --git a/src/components/__tests__/map.test.tsx b/src/components/__tests__/map.test.tsx index dd4f0484..6fc45acc 100644 --- a/src/components/__tests__/map.test.tsx +++ b/src/components/__tests__/map.test.tsx @@ -156,6 +156,55 @@ describe('creating and updating map instance', () => { const [, options] = createMapSpy.mock.lastCall!; expect(options).toMatchObject({mapId: 'othermapid'}); }); + + test('recreates the map when the colorScheme is changed', () => { + createMapSpy.mockReset(); + rerender( + + ); + + expect(createMapSpy).toHaveBeenCalled(); + + const [, options] = createMapSpy.mock.lastCall!; + expect(options).toMatchObject({colorScheme: 'DARK'}); + }); + + test('recreates the map when the renderingType is changed', () => { + createMapSpy.mockReset(); + rerender( + + ); + + expect(createMapSpy).toHaveBeenCalled(); + + const [, options] = createMapSpy.mock.lastCall!; + expect(options).toMatchObject({renderingType: 'VECTOR'}); + }); +}); + +describe('map instance caching', () => { + test.todo( + "map isn't recreated when unmounting and remounting with the same props" + ); + test.todo( + 'map is recreated when unmounting and remounting with changed mapId' + ); + test.todo( + "map isn't recreated when unmounting and remounting with regular changed options" + ); + test.todo('removed options are handled correctly'); }); describe('camera configuration', () => { diff --git a/src/components/info-window.tsx b/src/components/info-window.tsx index 926935a5..231876c5 100644 --- a/src/components/info-window.tsx +++ b/src/components/info-window.tsx @@ -1,6 +1,5 @@ /* eslint-disable complexity */ import React, { - ComponentType, CSSProperties, PropsWithChildren, ReactNode, diff --git a/src/components/map/index.tsx b/src/components/map/index.tsx index 556bed8a..ebb00d9e 100644 --- a/src/components/map/index.tsx +++ b/src/components/map/index.tsx @@ -43,10 +43,30 @@ export type MapCameraProps = { tilt?: number; }; +// ColorScheme and RenderingType are redefined here to make them usable before the +// maps API has been fully loaded. + +export const ColorScheme = { + DARK: 'DARK', + LIGHT: 'LIGHT', + FOLLOW_SYSTEM: 'FOLLOW_SYSTEM' +}; +export type ColorScheme = (typeof ColorScheme)[keyof typeof ColorScheme]; + +export const RenderingType = { + VECTOR: 'VECTOR', + RASTER: 'RASTER', + UNINITIALIZED: 'UNINITIALIZED' +}; +export type RenderingType = (typeof RenderingType)[keyof typeof RenderingType]; + /** * Props for the Map Component */ -export type MapProps = google.maps.MapOptions & +export type MapProps = Omit< + google.maps.MapOptions, + 'renderingType' | 'colorScheme' +> & MapEventProps & DeckGlCompatProps & { /** @@ -54,14 +74,27 @@ export type MapProps = google.maps.MapOptions & * in the same APIProvider context. */ id?: string; + /** * Additional style rules to apply to the map dom-element. */ style?: CSSProperties; + /** * Additional css class-name to apply to the element containing the map. */ className?: string; + + /** + * The color-scheme to use for the map. + */ + colorScheme?: ColorScheme; + + /** + * The rendering-type to be used. + */ + renderingType?: RenderingType; + /** * Indicates that the map will be controlled externally. Disables all controls provided by the map itself. */ diff --git a/src/components/map/use-map-instance.ts b/src/components/map/use-map-instance.ts index f8cf3dc1..8a42cd3d 100644 --- a/src/components/map/use-map-instance.ts +++ b/src/components/map/use-map-instance.ts @@ -72,6 +72,8 @@ export function useMapInstance( defaultHeading, defaultTilt, reuseMaps, + renderingType, + colorScheme, ...mapOptions } = props; @@ -114,8 +116,10 @@ export function useMapInstance( const {addMapInstance, removeMapInstance} = context; - const mapId = props.mapId; - const cacheKey = mapId || 'default'; + // note: colorScheme (upcoming feature) isn't yet in the typings, remove once that is fixed: + const {mapId} = props; + const cacheKey = `${mapId || 'default'}:${renderingType || 'default'}:${colorScheme || 'LIGHT'}`; + let mapDiv: HTMLElement; let map: google.maps.Map; @@ -133,7 +137,15 @@ export function useMapInstance( mapDiv = document.createElement('div'); mapDiv.style.height = '100%'; container.appendChild(mapDiv); - map = new google.maps.Map(mapDiv, mapOptions); + map = new google.maps.Map(mapDiv, { + ...mapOptions, + renderingType: renderingType as google.maps.RenderingType, + // The colorScheme option and google.maps.ColorScheme type haven't been added + // to the @types/google.maps package yet, so this will cause a TS error: + // @ts-expect-error TS2353: Object literal may only specify known properties, + // and colorScheme does not exist in type MapOptions + colorScheme: colorScheme as google.maps.ColorScheme + }); } setMap(map); @@ -186,7 +198,17 @@ export function useMapInstance( // changes should be ignored // - mapOptions has special hooks that take care of updating the options // eslint-disable-next-line react-hooks/exhaustive-deps - [container, apiIsLoaded, id, props.mapId] + [ + container, + apiIsLoaded, + id, + + // these props can't be changed after initialization and require a new + // instance to be created + props.mapId, + props.renderingType, + props.colorScheme + ] ); return [map, containerRef, cameraStateRef] as const; diff --git a/src/components/map/use-map-options.ts b/src/components/map/use-map-options.ts index 8c64e406..565be2d7 100644 --- a/src/components/map/use-map-options.ts +++ b/src/components/map/use-map-options.ts @@ -1,7 +1,7 @@ import {MapProps} from '../map'; import {useDeepCompareEffect} from '../../libraries/use-deep-compare-effect'; -const mapOptionKeys = new Set([ +const mapOptionKeys: Set = new Set([ 'backgroundColor', 'clickableIcons', 'controlSize', @@ -13,6 +13,7 @@ const mapOptionKeys = new Set([ 'fullscreenControl', 'fullscreenControlOptions', 'gestureHandling', + 'headingInteractionEnabled', 'isFractionalZoomEnabled', 'keyboardShortcuts', 'mapTypeControl', @@ -33,6 +34,7 @@ const mapOptionKeys = new Set([ 'streetViewControl', 'streetViewControlOptions', 'styles', + 'tiltInteractionEnabled', 'zoomControl', 'zoomControlOptions' ]);