@sanity/google-maps-input 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,173 +1,179 @@
1
- import React from 'react'
2
- import {uniqueId} from 'lodash'
1
+ import {useCallback, useEffect, useId, useRef, useState} from 'react'
3
2
  import {Box, Button, Dialog, Grid, Stack} from '@sanity/ui'
4
3
  import {EditIcon, TrashIcon} from '@sanity/icons'
5
- import {ObjectInputProps, set, setIfMissing, unset, ChangeIndicator} from 'sanity'
4
+ import {ObjectInputProps, set, setIfMissing, unset, ChangeIndicator, Path} from 'sanity'
6
5
  import {GoogleMapsLoadProxy} from '../loader/GoogleMapsLoadProxy'
7
- import {Geopoint, GeopointSchemaType, LatLng} from '../types'
8
- import {GeopointSelect} from './GeopointSelect'
9
- import {DialogInnerContainer, PreviewImage} from './GeopointInput.styles'
10
- import {GoogleMapsInputConfig} from '../index'
6
+ import type {Geopoint, GeopointSchemaType, GoogleMapsInputConfig, LatLng} from '../types'
11
7
  import {getGeoConfig} from '../global-workaround'
8
+ import {DialogInnerContainer, PreviewImage} from './GeopointInput.styles'
9
+ import {GeopointSelect} from './GeopointSelect'
10
+
11
+ const EMPTY_PATH: Path = []
12
12
 
13
13
  const getStaticImageUrl = (value: LatLng, apiKey: string) => {
14
14
  const loc = `${value.lat},${value.lng}`
15
- const params = {
15
+ const qs = new URLSearchParams({
16
16
  key: apiKey,
17
17
  center: loc,
18
18
  markers: loc,
19
- zoom: 13,
20
- scale: 2,
19
+ zoom: '13',
20
+ scale: '2',
21
21
  size: '640x300',
22
- } as const
23
- const qs = Object.keys(params).reduce((res, param) => {
24
- return res.concat(`${param}=${encodeURIComponent(params[param as keyof typeof params])}`)
25
- }, [] as string[])
22
+ })
26
23
 
27
- return `https://maps.googleapis.com/maps/api/staticmap?${qs.join('&')}`
24
+ return `https://maps.googleapis.com/maps/api/staticmap?${qs.toString()}`
28
25
  }
29
26
 
30
27
  export type GeopointInputProps = ObjectInputProps<Geopoint, GeopointSchemaType> & {
31
28
  geoConfig: GoogleMapsInputConfig
32
29
  }
33
30
 
34
- type Focusable = any
35
-
36
- interface InputState {
37
- modalOpen: boolean
38
- }
39
-
40
- class GeopointInput extends React.PureComponent<GeopointInputProps, InputState> {
41
- _geopointInputId = uniqueId('GeopointInput')
42
-
43
- editButton: Focusable | undefined
44
-
45
- constructor(props: GeopointInputProps) {
46
- super(props)
47
-
48
- this.state = {
49
- modalOpen: false,
50
- }
51
- }
52
-
53
- setEditButton = (el: Focusable) => {
54
- this.editButton = el
55
- }
56
-
57
- focus() {
58
- if (this.editButton) {
59
- this.editButton.focus()
60
- }
61
- }
62
-
63
- handleToggleModal = () => {
64
- this.setState((prevState) => ({modalOpen: !prevState.modalOpen}))
65
- }
66
-
67
- handleCloseModal = () => {
68
- this.setState({modalOpen: false})
69
- }
70
-
71
- handleChange = (latLng: google.maps.LatLng) => {
72
- const {schemaType, onChange} = this.props
73
- onChange([
74
- setIfMissing({
75
- _type: schemaType.name,
76
- }),
77
- set(latLng.lat(), ['lat']),
78
- set(latLng.lng(), ['lng']),
79
- ])
80
- }
81
-
82
- handleClear = () => {
83
- const {onChange} = this.props
31
+ export function GeopointInput(props: GeopointInputProps) {
32
+ const {
33
+ changed,
34
+ elementProps,
35
+ focused,
36
+ geoConfig: config,
37
+ onChange,
38
+ onPathFocus,
39
+ path,
40
+ readOnly,
41
+ schemaType,
42
+ value,
43
+ } = props
44
+
45
+ const {
46
+ id,
47
+ ref: inputRef,
48
+ onBlur: handleBlur,
49
+ onFocus: handleFocus,
50
+ 'aria-describedby': ariaDescribedBy,
51
+ } = elementProps
52
+
53
+ const schemaTypeName = schemaType.name
54
+ const dialogId = useId()
55
+ const dialogRef = useRef<HTMLDivElement | null>(null)
56
+ const handleFocusButton = useCallback(() => inputRef?.current?.focus(), [inputRef])
57
+ const [modalOpen, setModalOpen] = useState(false)
58
+
59
+ const handleCloseModal = useCallback(() => {
60
+ if (dialogRef.current) dialogRef.current.blur()
61
+ setModalOpen(false)
62
+ handleFocusButton()
63
+ }, [setModalOpen, handleFocusButton])
64
+
65
+ const handleToggleModal = useCallback(
66
+ () => setModalOpen((currentState) => !currentState),
67
+ [setModalOpen],
68
+ )
69
+
70
+ const handleChange = useCallback(
71
+ (latLng: google.maps.LatLng) => {
72
+ onChange([
73
+ setIfMissing({_type: schemaTypeName}),
74
+ set(latLng.lat(), ['lat']),
75
+ set(latLng.lng(), ['lng']),
76
+ ])
77
+ },
78
+ [schemaTypeName, onChange],
79
+ )
80
+
81
+ const handleClear = useCallback(() => {
84
82
  onChange(unset())
85
- }
83
+ }, [onChange])
86
84
 
87
- render() {
88
- const {value, readOnly, geoConfig: config, path, changed, focused} = this.props
89
-
90
- const {modalOpen} = this.state
91
-
92
- if (!config || !config.apiKey) {
93
- return (
94
- <div>
95
- <p>
96
- The <a href="https://sanity.io/docs/schema-types/geopoint-type">Geopoint type</a> needs
97
- a Google Maps API key with access to:
98
- </p>
99
- <ul>
100
- <li>Google Maps JavaScript API</li>
101
- <li>Google Places API Web Service</li>
102
- <li>Google Static Maps API</li>
103
- </ul>
104
- <p>
105
- Please enter the API key with access to these services in your googleMapsInput plugin
106
- config.
107
- </p>
108
- </div>
109
- )
85
+ useEffect(() => {
86
+ if (modalOpen) {
87
+ onPathFocus(EMPTY_PATH)
110
88
  }
89
+ }, [modalOpen, onPathFocus])
111
90
 
91
+ if (!config || !config.apiKey) {
112
92
  return (
113
- <Stack space={3}>
114
- {value && (
115
- <ChangeIndicator path={path} isChanged={changed} hasFocus={!!focused}>
116
- <PreviewImage src={getStaticImageUrl(value, config.apiKey)} alt="Map location" />
117
- </ChangeIndicator>
118
- )}
119
-
120
- <Box>
121
- <Grid columns={value ? 2 : 1} gap={3}>
93
+ <div>
94
+ <p>
95
+ The <a href="https://sanity.io/docs/schema-types/geopoint-type">Geopoint type</a> needs a
96
+ Google Maps API key with access to:
97
+ </p>
98
+ <ul>
99
+ <li>Google Maps JavaScript API</li>
100
+ <li>Google Places API Web Service</li>
101
+ <li>Google Static Maps API</li>
102
+ </ul>
103
+ <p>
104
+ Please enter the API key with access to these services in your googleMapsInput plugin
105
+ config.
106
+ </p>
107
+ </div>
108
+ )
109
+ }
110
+
111
+ return (
112
+ <Stack space={3}>
113
+ {value && (
114
+ <ChangeIndicator path={path} isChanged={changed} hasFocus={!!focused}>
115
+ <PreviewImage
116
+ src={getStaticImageUrl(value, config.apiKey)}
117
+ alt="Map location"
118
+ onClick={handleFocusButton}
119
+ onDoubleClick={handleToggleModal}
120
+ />
121
+ </ChangeIndicator>
122
+ )}
123
+
124
+ <Box>
125
+ <Grid columns={value ? 2 : 1} gap={3}>
126
+ <Button
127
+ aria-describedby={ariaDescribedBy}
128
+ disabled={readOnly}
129
+ icon={value && EditIcon}
130
+ id={id}
131
+ mode="ghost"
132
+ onClick={handleToggleModal}
133
+ onFocus={handleFocus}
134
+ padding={3}
135
+ ref={inputRef}
136
+ text={value ? 'Edit' : 'Set location'}
137
+ />
138
+
139
+ {value && (
122
140
  <Button
141
+ disabled={readOnly}
142
+ icon={TrashIcon}
123
143
  mode="ghost"
124
- icon={value && EditIcon}
144
+ onClick={handleClear}
125
145
  padding={3}
126
- ref={this.setEditButton}
127
- text={value ? 'Edit' : 'Set location'}
128
- onClick={this.handleToggleModal}
129
- disabled={readOnly}
146
+ text="Remove"
147
+ tone="critical"
130
148
  />
131
-
132
- {value && (
133
- <Button
134
- tone="critical"
135
- icon={TrashIcon}
136
- padding={3}
137
- mode="ghost"
138
- text={'Remove'}
139
- onClick={this.handleClear}
140
- disabled={readOnly}
141
- />
142
- )}
143
- </Grid>
144
- </Box>
145
-
146
- {modalOpen && (
147
- <Dialog
148
- id={`${this._geopointInputId}_dialog`}
149
- onClose={this.handleCloseModal}
150
- header="Place the marker on the map"
151
- width={1}
152
- >
153
- <DialogInnerContainer>
154
- <GoogleMapsLoadProxy config={getGeoConfig()}>
155
- {(api) => (
156
- <GeopointSelect
157
- api={api}
158
- value={value || undefined}
159
- onChange={readOnly ? undefined : this.handleChange}
160
- defaultLocation={config.defaultLocation}
161
- defaultZoom={config.defaultZoom}
162
- />
163
- )}
164
- </GoogleMapsLoadProxy>
165
- </DialogInnerContainer>
166
- </Dialog>
167
- )}
168
- </Stack>
169
- )
170
- }
149
+ )}
150
+ </Grid>
151
+ </Box>
152
+
153
+ {modalOpen && (
154
+ <Dialog
155
+ header="Place the marker on the map"
156
+ id={`${dialogId}_dialog`}
157
+ onBlur={handleBlur}
158
+ onClose={handleCloseModal}
159
+ ref={dialogRef}
160
+ width={1}
161
+ >
162
+ <DialogInnerContainer>
163
+ <GoogleMapsLoadProxy config={getGeoConfig()}>
164
+ {(api) => (
165
+ <GeopointSelect
166
+ api={api}
167
+ value={value || undefined}
168
+ onChange={readOnly ? undefined : handleChange}
169
+ defaultLocation={config.defaultLocation}
170
+ defaultZoom={config.defaultZoom}
171
+ />
172
+ )}
173
+ </GoogleMapsLoadProxy>
174
+ </DialogInnerContainer>
175
+ </Dialog>
176
+ )}
177
+ </Stack>
178
+ )
171
179
  }
172
-
173
- export default GeopointInput
@@ -1,8 +1,8 @@
1
- import React from 'react'
1
+ import {type FC, useCallback} from 'react'
2
2
  import {SearchInput} from '../map/SearchInput'
3
3
  import {GoogleMap} from '../map/Map'
4
4
  import {Marker} from '../map/Marker'
5
- import {LatLng, Geopoint} from '../types'
5
+ import type {LatLng, Geopoint} from '../types'
6
6
 
7
7
  const fallbackLatLng: LatLng = {lat: 40.7058254, lng: -74.1180863}
8
8
 
@@ -14,65 +14,66 @@ interface SelectProps {
14
14
  defaultZoom?: number
15
15
  }
16
16
 
17
- export class GeopointSelect extends React.PureComponent<SelectProps> {
18
- static defaultProps = {
19
- defaultZoom: 8,
20
- defaultLocation: {lng: 10.74609, lat: 59.91273},
21
- }
22
-
23
- mapRef = React.createRef<HTMLDivElement>()
24
-
25
- getCenter() {
26
- const {value = {}, defaultLocation = {}} = this.props
17
+ export const GeopointSelect: FC<SelectProps> = ({
18
+ api,
19
+ value,
20
+ onChange,
21
+ defaultLocation = {lng: 10.74609, lat: 59.91273},
22
+ defaultZoom = 8,
23
+ }) => {
24
+ const getCenter = useCallback(() => {
27
25
  const point: LatLng = {...fallbackLatLng, ...defaultLocation, ...value}
28
26
  return point
29
- }
30
-
31
- handlePlaceChanged = (place: google.maps.places.PlaceResult) => {
32
- if (!place.geometry?.location) {
33
- return
34
- }
27
+ }, [value, defaultLocation])
35
28
 
36
- this.setValue(place.geometry.location)
37
- }
29
+ const setValue = useCallback(
30
+ (geoPoint: google.maps.LatLng) => {
31
+ if (onChange) {
32
+ onChange(geoPoint)
33
+ }
34
+ },
35
+ [onChange],
36
+ )
38
37
 
39
- handleMarkerDragEnd = (event: google.maps.MapMouseEvent) => {
40
- if (event.latLng) this.setValue(event.latLng)
41
- }
38
+ const handlePlaceChanged = useCallback(
39
+ (place: google.maps.places.PlaceResult) => {
40
+ if (!place.geometry?.location) {
41
+ return
42
+ }
43
+ setValue(place.geometry.location)
44
+ },
45
+ [setValue],
46
+ )
42
47
 
43
- handleMapClick = (event: google.maps.MapMouseEvent) => {
44
- if (event.latLng) this.setValue(event.latLng)
45
- }
48
+ const handleMarkerDragEnd = useCallback(
49
+ (event: google.maps.MapMouseEvent) => {
50
+ if (event.latLng) setValue(event.latLng)
51
+ },
52
+ [setValue],
53
+ )
46
54
 
47
- setValue(geoPoint: google.maps.LatLng) {
48
- if (this.props.onChange) {
49
- this.props.onChange(geoPoint)
50
- }
51
- }
55
+ const handleMapClick = useCallback(
56
+ (event: google.maps.MapMouseEvent) => {
57
+ if (event.latLng) setValue(event.latLng)
58
+ },
59
+ [setValue],
60
+ )
52
61
 
53
- render() {
54
- const {api, defaultZoom, value, onChange} = this.props
55
- return (
56
- <GoogleMap
57
- api={api}
58
- location={this.getCenter()}
59
- onClick={this.handleMapClick}
60
- defaultZoom={defaultZoom}
61
- >
62
- {(map) => (
63
- <>
64
- <SearchInput api={api} map={map} onChange={this.handlePlaceChanged} />
65
- {value && (
66
- <Marker
67
- api={api}
68
- map={map}
69
- position={value}
70
- onMove={onChange ? this.handleMarkerDragEnd : undefined}
71
- />
72
- )}
73
- </>
74
- )}
75
- </GoogleMap>
76
- )
77
- }
62
+ return (
63
+ <GoogleMap api={api} location={getCenter()} onClick={handleMapClick} defaultZoom={defaultZoom}>
64
+ {(map) => (
65
+ <>
66
+ <SearchInput api={api} map={map} onChange={handlePlaceChanged} />
67
+ {value && (
68
+ <Marker
69
+ api={api}
70
+ map={map}
71
+ position={value}
72
+ onMove={onChange ? handleMarkerDragEnd : undefined}
73
+ />
74
+ )}
75
+ </>
76
+ )}
77
+ </GoogleMap>
78
+ )
78
79
  }
@@ -1,10 +1,10 @@
1
- import React, {useEffect, useState} from 'react'
1
+ import {type ReactElement, useEffect, useState} from 'react'
2
+ import type {GoogleMapsInputConfig} from '../types'
2
3
  import {AuthError, loadGoogleMapsApi} from './loadGoogleMapsApi'
3
4
  import {LoadError as LoadErrorView} from './LoadError'
4
- import {GoogleMapsInputConfig} from '../index'
5
5
 
6
6
  interface LoadProps {
7
- children: (api: typeof window.google.maps) => React.ReactElement
7
+ children: (api: typeof window.google.maps) => ReactElement
8
8
  config: GoogleMapsInputConfig
9
9
  }
10
10
 
@@ -29,6 +29,10 @@ function useLoadGoogleMapsApi(config: {defaultLocale?: string; apiKey: string}):
29
29
  const [state, setState] = useState<LoadState>({type: 'loading'})
30
30
 
31
31
  useEffect(() => {
32
+ if (typeof window === 'undefined') {
33
+ return
34
+ }
35
+
32
36
  loadGoogleMapsApi({locale, apiKey: config.apiKey}).then(
33
37
  (api) => setState({type: 'loaded', api}),
34
38
  (err) =>
@@ -1,4 +1,3 @@
1
- import * as React from 'react'
2
1
  import {Card, Box, Text, Code} from '@sanity/ui'
3
2
 
4
3
  type Props = {error: {message?: string}; isAuthError: false} | {isAuthError: true}
package/src/map/Arrow.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import * as React from 'react'
2
- import {LatLng} from '../types'
1
+ import {type MutableRefObject, PureComponent} from 'react'
2
+ import type {LatLng} from '../types'
3
3
  import {latLngAreEqual} from './util'
4
4
 
5
5
  interface Props {
@@ -9,11 +9,11 @@ interface Props {
9
9
  to: LatLng
10
10
  color?: {background: string; border: string; text: string}
11
11
  zIndex?: number
12
- arrowRef?: React.MutableRefObject<google.maps.Polyline | undefined>
12
+ arrowRef?: MutableRefObject<google.maps.Polyline | undefined>
13
13
  onClick?: (event: google.maps.MapMouseEvent) => void
14
14
  }
15
15
 
16
- export class Arrow extends React.PureComponent<Props> {
16
+ export class Arrow extends PureComponent<Props> {
17
17
  line: google.maps.Polyline | undefined
18
18
 
19
19
  eventHandlers: {
package/src/map/Map.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import React from 'react'
2
- import {LatLng} from '../types'
1
+ import {createRef, PureComponent, type ReactElement} from 'react'
2
+ import type {LatLng} from '../types'
3
3
  import {latLngAreEqual} from './util'
4
4
  import {MapContainer} from './Map.styles'
5
5
 
@@ -12,14 +12,14 @@ interface MapProps {
12
12
  scrollWheel?: boolean
13
13
  controlSize?: number
14
14
  onClick?: (event: google.maps.MapMouseEvent) => void
15
- children?: (map: google.maps.Map) => React.ReactElement
15
+ children?: (map: google.maps.Map) => ReactElement
16
16
  }
17
17
 
18
18
  interface MapState {
19
19
  map: google.maps.Map | undefined
20
20
  }
21
21
 
22
- export class GoogleMap extends React.PureComponent<MapProps, MapState> {
22
+ export class GoogleMap extends PureComponent<MapProps, MapState> {
23
23
  static defaultProps = {
24
24
  defaultZoom: 8,
25
25
  scrollWheel: true,
@@ -27,7 +27,7 @@ export class GoogleMap extends React.PureComponent<MapProps, MapState> {
27
27
 
28
28
  state: MapState = {map: undefined}
29
29
  clickHandler: google.maps.MapsEventListener | undefined
30
- mapRef = React.createRef<HTMLDivElement>()
30
+ mapRef = createRef<HTMLDivElement>()
31
31
  mapEl: HTMLDivElement | null = null
32
32
 
33
33
  componentDidMount() {
@@ -1,5 +1,5 @@
1
- import * as React from 'react'
2
- import {LatLng} from '../types'
1
+ import {PureComponent, type MutableRefObject} from 'react'
2
+ import type {LatLng} from '../types'
3
3
  import {latLngAreEqual} from './util'
4
4
 
5
5
  const markerPath =
@@ -14,11 +14,11 @@ interface Props {
14
14
  zIndex?: number
15
15
  opacity?: number
16
16
  label?: string
17
- markerRef?: React.MutableRefObject<google.maps.Marker | undefined>
17
+ markerRef?: MutableRefObject<google.maps.Marker | undefined>
18
18
  color?: {background: string; border: string; text: string}
19
19
  }
20
20
 
21
- export class Marker extends React.PureComponent<Props> {
21
+ export class Marker extends PureComponent<Props> {
22
22
  marker: google.maps.Marker | undefined
23
23
 
24
24
  eventHandlers: {
@@ -1,6 +1,6 @@
1
- import * as React from 'react'
2
1
  import {TextInput} from '@sanity/ui'
3
2
  import {WrapperContainer} from './SearchInput.styles'
3
+ import {createRef, PureComponent} from 'react'
4
4
 
5
5
  interface Props {
6
6
  api: typeof window.google.maps
@@ -8,8 +8,8 @@ interface Props {
8
8
  onChange: (result: google.maps.places.PlaceResult) => void
9
9
  }
10
10
 
11
- export class SearchInput extends React.PureComponent<Props> {
12
- searchInputRef = React.createRef<HTMLInputElement>()
11
+ export class SearchInput extends PureComponent<Props> {
12
+ searchInputRef = createRef<HTMLInputElement>()
13
13
  autoComplete: google.maps.places.Autocomplete | undefined
14
14
 
15
15
  handleChange = () => {
package/src/map/util.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {LatLng} from '../types'
1
+ import type {LatLng} from '../types'
2
2
 
3
3
  export function latLngAreEqual(
4
4
  latLng1: LatLng | google.maps.LatLng,
package/src/plugin.tsx CHANGED
@@ -1,18 +1,7 @@
1
- import React from 'react'
2
- import {definePlugin, SchemaType} from 'sanity'
3
- import GeopointInput, {GeopointInputProps} from './input/GeopointInput'
1
+ import {definePlugin, type SchemaType} from 'sanity'
2
+ import {GeopointInput, type GeopointInputProps} from './input/GeopointInput'
4
3
  import {setGeoConfig} from './global-workaround'
5
- import {GeopointSchemaType} from './types'
6
-
7
- export interface GoogleMapsInputConfig {
8
- apiKey: string
9
- defaultZoom?: number
10
- defaultLocale?: string
11
- defaultLocation?: {
12
- lat: number
13
- lng: number
14
- }
15
- }
4
+ import type {GeopointSchemaType, GoogleMapsInputConfig} from './types'
16
5
 
17
6
  export const googleMapsInput = definePlugin<GoogleMapsInputConfig>((config) => {
18
7
  setGeoConfig(config)
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
- import {DiffComponent, DiffComponentOptions, ObjectDiff} from 'sanity'
2
- import {ObjectSchemaType} from 'sanity'
1
+ import type {DiffComponent, DiffComponentOptions, ObjectDiff} from 'sanity'
2
+ import type {ObjectSchemaType} from 'sanity'
3
3
 
4
4
  export interface LatLng {
5
5
  lat: number
@@ -17,3 +17,13 @@ export interface Geopoint {
17
17
  export interface GeopointSchemaType extends ObjectSchemaType {
18
18
  diffComponent?: DiffComponent<ObjectDiff<Geopoint>> | DiffComponentOptions<ObjectDiff<Geopoint>>
19
19
  }
20
+
21
+ export interface GoogleMapsInputConfig {
22
+ apiKey: string
23
+ defaultZoom?: number
24
+ defaultLocale?: string
25
+ defaultLocation?: {
26
+ lat: number
27
+ lng: number
28
+ }
29
+ }
@@ -1,7 +0,0 @@
1
- import cjs from './index.js';
2
-
3
- export const GeopointArrayDiff = cjs.GeopointArrayDiff;
4
- export const GeopointFieldDiff = cjs.GeopointFieldDiff;
5
- export const GeopointInput = cjs.GeopointInput;
6
- export const googleMapsInput = cjs.googleMapsInput;
7
-