Skip to content

Commit dda245a

Browse files
committed
feat: add AdvancedMarker
1 parent c2e8d4e commit dda245a

15 files changed

Lines changed: 387 additions & 5 deletions

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
11
# React Google Map Wrapper
22

33
`react-google-map-wrapper` is a React component library for rendering Google Maps.
4+
5+
## TO DO
6+
7+
- [x] GoogleMapApiLoader
8+
- [x] GoogleMap
9+
- [x] Marker
10+
- [x] Custom Marker
11+
- [x] Controls
12+
- [x] InfoWindow
13+
- [x] Polyline
14+
- [x] Polygon
15+
- [x] Rectangle
16+
- [x] Circle
17+
- [x] AdvancedMarker
18+
- [x] MarkerClusterer
19+
- [x] HeatmapLayer
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ComponentProps, createContext, useContext } from 'react';
2+
3+
export interface AdvancedMarkerContentContextData {
4+
setAdvancedMarkerContent: (
5+
content: google.maps.marker.PinElement | null,
6+
) => void;
7+
}
8+
9+
const AdvancedMarkerContentContext =
10+
createContext<AdvancedMarkerContentContextData>({
11+
setAdvancedMarkerContent: () => {},
12+
});
13+
14+
export const useAdvancedMarkerContent = () =>
15+
useContext(AdvancedMarkerContentContext);
16+
17+
export function AdvancedMarkerContentProvider({
18+
children,
19+
...props
20+
}: ComponentProps<typeof AdvancedMarkerContentContext.Provider>) {
21+
return (
22+
<AdvancedMarkerContentContext.Provider {...props}>
23+
{children}
24+
</AdvancedMarkerContentContext.Provider>
25+
);
26+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
2+
3+
import { createPortal } from 'react-dom';
4+
5+
import {
6+
AdvancedMarkerContentContextData,
7+
AdvancedMarkerContentProvider,
8+
} from './Context';
9+
import { AdvancedMarkerProps } from './type';
10+
import { useApplyAdvancedMarkerEvent } from '../../hooks/useApplyAdvancedMarkerEvent';
11+
import { useApplyAdvancedMarkerOptions } from '../../hooks/useApplyAdvancedMarkerOptions';
12+
import { useImportLibrary } from '../../hooks/useImportLibrary';
13+
import { passRef } from '../../utils/passRef';
14+
import { useSetAnchor } from '../InfoWindow/Context';
15+
import { useMapContext } from '../Provider/MapProvider';
16+
17+
export const AdvancedMarker = forwardRef<
18+
google.maps.marker.AdvancedMarkerElement,
19+
AdvancedMarkerProps
20+
>(function AdvancedMarker(
21+
{
22+
children,
23+
lat,
24+
lng,
25+
collisionBehavior,
26+
content,
27+
gmpDraggable,
28+
title,
29+
zIndex,
30+
hidden = false,
31+
onClick,
32+
onDrag,
33+
onDragEnd,
34+
onDragStart,
35+
onGmpClick,
36+
},
37+
ref,
38+
) {
39+
const originalMap = useMapContext();
40+
const map = hidden ? null : originalMap;
41+
const markerLib = useImportLibrary('marker');
42+
const setAnchor = useSetAnchor();
43+
44+
const fragment = useRef<HTMLDivElement>(document.createElement('div'));
45+
const [advancedMarker, setAdvancedMarker] =
46+
useState<google.maps.marker.AdvancedMarkerElement | null>(null);
47+
const [pinElement, setPinElement] =
48+
useState<google.maps.marker.PinElement | null>(null);
49+
50+
const value = useMemo<AdvancedMarkerContentContextData>(
51+
() => ({
52+
setAdvancedMarkerContent: setPinElement,
53+
}),
54+
[],
55+
);
56+
57+
useEffect(() => {
58+
if (!markerLib?.AdvancedMarkerElement) {
59+
return;
60+
}
61+
62+
fragment.current.style.display = 'contents';
63+
64+
const advancedMarker = new markerLib.AdvancedMarkerElement({
65+
collisionBehavior,
66+
content: pinElement?.element ?? (children ? fragment.current : content),
67+
gmpDraggable,
68+
title,
69+
zIndex,
70+
map,
71+
position: { lat, lng },
72+
});
73+
74+
setAdvancedMarker(advancedMarker);
75+
passRef(ref, advancedMarker);
76+
77+
// for InfoWindow
78+
setAnchor(advancedMarker);
79+
80+
return () => {
81+
advancedMarker.map = null;
82+
setAnchor(null);
83+
};
84+
}, [markerLib?.AdvancedMarkerElement]);
85+
86+
useEffect(() => {
87+
if (advancedMarker) {
88+
advancedMarker.map = map;
89+
}
90+
}, [hidden]);
91+
92+
useApplyAdvancedMarkerOptions(advancedMarker, {
93+
lat,
94+
lng,
95+
collisionBehavior,
96+
gmpDraggable,
97+
title,
98+
zIndex,
99+
content: pinElement?.element ?? (children ? fragment.current : content),
100+
});
101+
102+
useApplyAdvancedMarkerEvent(advancedMarker, {
103+
onClick,
104+
onDrag,
105+
onDragEnd,
106+
onDragStart,
107+
onGmpClick,
108+
});
109+
110+
return (
111+
<AdvancedMarkerContentProvider value={value}>
112+
{createPortal(children, fragment.current)}
113+
</AdvancedMarkerContentProvider>
114+
);
115+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { ReactNode } from 'react';
2+
3+
export interface AdvancedMarkerEvent {
4+
onClick?: (
5+
marker: google.maps.marker.AdvancedMarkerElement,
6+
event: google.maps.MapMouseEvent,
7+
) => void;
8+
onDrag?: (
9+
marker: google.maps.marker.AdvancedMarkerElement,
10+
event: google.maps.MapMouseEvent,
11+
) => void;
12+
onDragEnd?: (
13+
marker: google.maps.marker.AdvancedMarkerElement,
14+
event: google.maps.MapMouseEvent,
15+
) => void;
16+
onDragStart?: (
17+
marker: google.maps.marker.AdvancedMarkerElement,
18+
event: google.maps.MapMouseEvent,
19+
) => void;
20+
onGmpClick?: (
21+
marker: google.maps.marker.AdvancedMarkerElement,
22+
evnet: google.maps.marker.AdvancedMarkerClickEvent,
23+
) => void;
24+
}
25+
26+
export interface AdvancedMarkerProps
27+
extends Omit<
28+
google.maps.marker.AdvancedMarkerElementOptions,
29+
'position' | 'map'
30+
>,
31+
AdvancedMarkerEvent {
32+
lat: number;
33+
lng: number;
34+
children?: Exclude<ReactNode, Iterable<ReactNode>>;
35+
hidden?: boolean;
36+
}

src/components/HeatmapLayer/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ export const HeatmapLayer = forwardRef<
2020
},
2121
ref,
2222
) {
23-
const map = useMapContext();
23+
const originalMap = useMapContext();
24+
const map = hidden ? null : originalMap;
2425
const visualizationLib = useImportLibrary('visualization');
2526
const [heatmap, setHeatmap] =
2627
useState<google.maps.visualization.HeatmapLayer | null>(null);
@@ -55,7 +56,7 @@ export const HeatmapLayer = forwardRef<
5556
}, [data]);
5657

5758
useEffect(() => {
58-
heatmap?.setMap(hidden ? null : map);
59+
heatmap?.setMap(map);
5960
}, [hidden]);
6061

6162
useEffect(() => {

src/components/Marker/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export const Marker = forwardRef<google.maps.Marker, MarkerProps>(
4343
const markerLib = useImportLibrary('marker');
4444
const setAnchor = useSetAnchor();
4545
const cluster = useMarkerClusterer();
46-
const isFirstMount = useRef(true);
4746

4847
const [marker, setMarker] = useState<google.maps.Marker | null>(null);
4948

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { forwardRef, useEffect, useRef, useState } from 'react';
2+
3+
import { PinElementProps } from './type';
4+
import { useImportLibrary } from '../../hooks/useImportLibrary';
5+
import { passRef } from '../../utils/passRef';
6+
import { useApplyPinElementOptions } from '../../hooks/useApplyPinElementOptions';
7+
import { useAdvancedMarkerContent } from '../AdvancedMarker/Context';
8+
import { createPortal } from 'react-dom';
9+
10+
const PinElementImpl = forwardRef<
11+
google.maps.marker.PinElement,
12+
PinElementProps & { lib: google.maps.MarkerLibrary }
13+
>(function PinElementImpl(
14+
{ children, lib, background, borderColor, glyph, glyphColor, scale },
15+
ref,
16+
) {
17+
const { setAdvancedMarkerContent } = useAdvancedMarkerContent();
18+
19+
const fragment = useRef<HTMLDivElement>(document.createElement('div'));
20+
const [pinElement, setPinElement] =
21+
useState<google.maps.marker.PinElement | null>(null);
22+
23+
useEffect(() => {
24+
fragment.current.style.display = 'contents';
25+
const pinElement = new lib.PinElement({
26+
background,
27+
borderColor,
28+
glyph: fragment.current.childNodes.length ? fragment.current : glyph,
29+
glyphColor,
30+
scale,
31+
});
32+
33+
setPinElement(pinElement);
34+
passRef(ref, pinElement);
35+
36+
// for InfoWindow
37+
setAdvancedMarkerContent(pinElement);
38+
39+
return () => {
40+
setAdvancedMarkerContent(null);
41+
};
42+
}, []);
43+
44+
useApplyPinElementOptions(pinElement, {
45+
background,
46+
borderColor,
47+
glyph: fragment.current.childNodes.length ? fragment.current : glyph,
48+
glyphColor,
49+
scale,
50+
});
51+
52+
return createPortal(<>{children}</>, fragment.current);
53+
});
54+
55+
export const PinElement = forwardRef<
56+
google.maps.marker.PinElement,
57+
PinElementProps
58+
>(function PinElement(props, ref) {
59+
const markerLib = useImportLibrary('marker');
60+
61+
if (!markerLib?.PinElement) {
62+
return null;
63+
}
64+
65+
return (
66+
<>
67+
<PinElementImpl ref={ref} lib={markerLib} {...props} />
68+
</>
69+
);
70+
});

src/components/PinElement/type.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ReactNode } from 'react';
2+
3+
export interface PinElementProps extends google.maps.marker.PinElementOptions {
4+
children?: Exclude<ReactNode, Iterable<ReactNode>>;
5+
}

src/components/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export * from './Circle/index';
2020
export * from './Circle/type';
2121
export * from './HeatmapLayer/index';
2222
export * from './HeatmapLayer/type';
23+
export * from './AdvancedMarker/index';
24+
export * from './AdvancedMarker/type';
25+
export * from './PinElement/index';
26+
export * from './PinElement/type';
2327
export * from './Provider/MapProvider';
2428
export * from './Provider/MarkerProvider';
2529
export * from './Provider/CustomMarkerProvider';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { AdvancedMarkerEvent } from 'src/components/AdvancedMarker/type';
2+
import { useMvcObjectEventEffect } from './useMvcObjectEventEffect';
3+
4+
export const useApplyAdvancedMarkerEvent = (
5+
advancedMarker: google.maps.marker.AdvancedMarkerElement | null,
6+
{ onClick, onDrag, onDragEnd, onDragStart, onGmpClick }: AdvancedMarkerEvent,
7+
) => {
8+
useMvcObjectEventEffect(advancedMarker, 'click', onClick);
9+
useMvcObjectEventEffect(advancedMarker, 'drag', onDrag);
10+
useMvcObjectEventEffect(advancedMarker, 'dragend', onDragEnd);
11+
useMvcObjectEventEffect(advancedMarker, 'drag_start', onDragStart);
12+
useMvcObjectEventEffect(advancedMarker, 'gmp_click', onGmpClick);
13+
};

0 commit comments

Comments
 (0)