diff --git a/docs/api-reference/components/advanced-marker.md b/docs/api-reference/components/advanced-marker.md index e9162048..d16bae62 100644 --- a/docs/api-reference/components/advanced-marker.md +++ b/docs/api-reference/components/advanced-marker.md @@ -203,6 +203,9 @@ marker will be clickable and will be interactive for accessibility purposes By default, this will automatically be set to true when the `onClick` prop is specified. +It is also automatically set to true when `onKeyDown` or `onKeyUp` is +specified. + #### `draggable`: boolean Controls if the marker can be repositioned by dragging. @@ -223,6 +226,14 @@ specified in the position can't be dragged. This event is fired when the marker is clicked. +#### `onKeyDown`: (e: KeyboardEvent) => void + +This event is fired when a key is pressed while the marker is focused. + +#### `onKeyUp`: (e: KeyboardEvent) => void + +This event is fired when a pressed key is released while the marker is focused. + #### `onMouseEnter`: (e: [google.maps.MapMouseEvent['domEvent']][gmp-map-mouse-ev-dom]) => void This event is fired when the mouse enters the marker. diff --git a/src/components/__tests__/advanced-marker.test.tsx b/src/components/__tests__/advanced-marker.test.tsx index 197a9fa5..e6727436 100644 --- a/src/components/__tests__/advanced-marker.test.tsx +++ b/src/components/__tests__/advanced-marker.test.tsx @@ -1,7 +1,12 @@ import React from 'react'; import {initialize, mockInstances} from '@googlemaps/jest-mocks'; -import {cleanup, queryByTestId, render} from '@testing-library/react'; +import { + cleanup, + fireEvent, + queryByTestId, + render +} from '@testing-library/react'; import '@testing-library/jest-dom'; import {AdvancedMarker, AdvancedMarkerAnchorPoint} from '../advanced-marker'; @@ -195,8 +200,53 @@ describe('map and marker-library loaded', () => { expect((marker.content as HTMLElement).style.cursor).toBe(''); }); + test('binds gmp-click events to onClick', async () => { + const handleClick = jest.fn(); + + render( + + ); + + const marker = await waitForMockInstance( + google.maps.marker.AdvancedMarkerElement + ); + + const clickEvent = new Event('gmp-click'); + fireEvent(marker, clickEvent); + + expect(handleClick).toHaveBeenCalledWith(clickEvent); + expect(marker.gmpClickable).toBe(true); + }); + + test('binds keyboard events and infers clickability from them', async () => { + const handleKeyDown = jest.fn(); + const handleKeyUp = jest.fn(); + + render( + + ); + + const marker = await waitForMockInstance( + google.maps.marker.AdvancedMarkerElement + ); + + expect(marker.gmpClickable).toBe(true); + + const keyDownEvent = new KeyboardEvent('keydown', {key: 'Enter'}); + const keyUpEvent = new KeyboardEvent('keyup', {key: ' '}); + + fireEvent(marker, keyDownEvent); + fireEvent(marker, keyUpEvent); + + expect(handleKeyDown).toHaveBeenCalledWith(keyDownEvent); + expect(handleKeyUp).toHaveBeenCalledWith(keyUpEvent); + }); + test.todo('marker should work with options'); - test.todo('marker should have a click listener'); describe('anchoring with modern API', () => { beforeEach(() => { diff --git a/src/components/advanced-marker.tsx b/src/components/advanced-marker.tsx index a240b273..10a9c210 100644 --- a/src/components/advanced-marker.tsx +++ b/src/components/advanced-marker.tsx @@ -75,7 +75,9 @@ export type AdvancedMarkerAnchorPoint = (typeof AdvancedMarkerAnchorPoint)[keyof typeof AdvancedMarkerAnchorPoint]; type AdvancedMarkerEventProps = { - onClick?: (e: google.maps.MapMouseEvent) => void; + onClick?: (e: google.maps.marker.AdvancedMarkerClickEvent) => void; + onKeyDown?: (e: KeyboardEvent) => void; + onKeyUp?: (e: KeyboardEvent) => void; onMouseEnter?: (e: google.maps.MapMouseEvent['domEvent']) => void; onMouseLeave?: (e: google.maps.MapMouseEvent['domEvent']) => void; onDrag?: (e: google.maps.MapMouseEvent) => void; @@ -211,6 +213,8 @@ function useAdvancedMarker(props: AdvancedMarkerProps) { const { children, onClick, + onKeyDown, + onKeyUp, className, onMouseEnter, onMouseLeave, @@ -296,8 +300,8 @@ function useAdvancedMarker(props: AdvancedMarkerProps) { else marker.gmpDraggable = false; }, [marker, draggable, onDrag, onDragEnd, onDragStart]); - // set gmpClickable from props (when unspecified, it's true if the onClick or one of - // the hover events callbacks are specified) + // set gmpClickable from props (when unspecified, it's true if any interactive + // event callbacks are specified) useEffect(() => { if (!marker) return; @@ -306,7 +310,11 @@ function useAdvancedMarker(props: AdvancedMarkerProps) { const gmpClickable = clickable !== undefined ? clickable - : Boolean(onClick) || Boolean(onMouseEnter) || Boolean(onMouseLeave); + : Boolean(onClick) || + Boolean(onKeyDown) || + Boolean(onKeyUp) || + Boolean(onMouseEnter) || + Boolean(onMouseLeave); // gmpClickable is only available in beta version of the // maps api (as of 2024-10-10) @@ -319,13 +327,23 @@ function useAdvancedMarker(props: AdvancedMarkerProps) { marker.content.style.pointerEvents = gmpClickable ? 'all' : 'none'; marker.content.style.cursor = gmpClickable && onClick ? 'pointer' : ''; } - }, [marker, clickable, onClick, onMouseEnter, onMouseLeave]); + }, [ + marker, + clickable, + onClick, + onKeyDown, + onKeyUp, + onMouseEnter, + onMouseLeave + ]); useMapsEventListener(marker, 'drag', onDrag); useMapsEventListener(marker, 'dragstart', onDragStart); useMapsEventListener(marker, 'dragend', onDragEnd); useDomEventListener(marker, 'gmp-click', onClick); + useDomEventListener(marker, 'keydown', onKeyDown); + useDomEventListener(marker, 'keyup', onKeyUp); useDomEventListener(marker, 'mouseenter', onMouseEnter); useDomEventListener(marker, 'mouseleave', onMouseLeave);