-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathAddressInputAutocomplete.tsx
More file actions
129 lines (117 loc) · 4.15 KB
/
AddressInputAutocomplete.tsx
File metadata and controls
129 lines (117 loc) · 4.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import styles from './AddressInputAutocomplete.module.css'
import { Bbox } from '@/api/graphhopper'
import { AddressParseResult } from '@/pois/AddressParseResult'
export interface AutocompleteItem {}
export class GeocodingItem implements AutocompleteItem {
mainText: string
secondText: string
street: string
point: { lat: number; lng: number }
bbox: Bbox
constructor(mainText: string, secondText: string, street: string, point: { lat: number; lng: number }, bbox: Bbox) {
this.mainText = mainText
this.secondText = secondText
this.street = street
this.point = point
this.bbox = bbox
}
toText() {
return this.mainText + ', ' + this.secondText
}
}
export class POIQueryItem implements AutocompleteItem {
result: AddressParseResult
constructor(result: AddressParseResult) {
this.result = result
}
}
export interface AutocompleteProps {
items: AutocompleteItem[]
highlightedItem: AutocompleteItem
onSelect: (hit: AutocompleteItem) => void
}
export default function Autocomplete({ items, highlightedItem, onSelect }: AutocompleteProps) {
return (
<ul>
{items.map((item, i) => (
<li key={i} className={styles.autocompleteItem}>
{mapToComponent(item, highlightedItem === item, onSelect)}
</li>
))}
</ul>
)
}
function mapToComponent(item: AutocompleteItem, isHighlighted: boolean, onSelect: (hit: AutocompleteItem) => void) {
if (item instanceof GeocodingItem)
return <GeocodingEntry item={item} isHighlighted={isHighlighted} onSelect={onSelect} />
else if (item instanceof POIQueryItem)
return <POIQueryEntry item={item} isHighlighted={isHighlighted} onSelect={onSelect} />
else throw Error('Unsupported item type: ' + typeof item)
}
export function POIQueryEntry({
item,
isHighlighted,
onSelect,
}: {
item: POIQueryItem
isHighlighted: boolean
onSelect: (item: POIQueryItem) => void
}) {
const poi = item.result.poiType ? item.result.poiType : ''
return (
<AutocompleteEntry isHighlighted={isHighlighted} onSelect={() => onSelect(item)}>
<div className={styles.poiEntry}>
<span className={styles.poiEntryPrimaryText}>{poi.charAt(0).toUpperCase() + poi.slice(1)}</span>
<span>{item.result.text('')}</span>
</div>
</AutocompleteEntry>
)
}
function GeocodingEntry({
item,
isHighlighted,
onSelect,
}: {
item: GeocodingItem
isHighlighted: boolean
onSelect: (item: GeocodingItem) => void
}) {
return (
<AutocompleteEntry isHighlighted={isHighlighted} onSelect={() => onSelect(item)}>
<div className={styles.geocodingEntry} title={item.toText()}>
<span className={styles.mainText}>{item.mainText}</span>
<span className={styles.secondaryText}>{item.secondText}</span>
</div>
</AutocompleteEntry>
)
}
function AutocompleteEntry({
isHighlighted,
children,
onSelect,
}: {
isHighlighted: boolean
children: React.ReactNode
onSelect: () => void
}) {
const className = isHighlighted ? styles.selectableItem + ' ' + styles.highlightedItem : styles.selectableItem
return (
<button
className={className}
// using click events for mouse interaction and touch end to select an entry.
onClick={() => onSelect()}
// minor workaround to improve success rate for click even if start and end location on screen are slightly different
onTouchEnd={e => {
e.preventDefault() // do not forward click to underlying component
onSelect()
}}
onMouseDown={e => {
// prevents that input->onBlur is called when clicking the autocomplete item (focus would be lost and autocomplete items would disappear before they can be clicked)
// See also the onMouseDown calls in the buttons in AddressInput.tsx created for the same reason.
e.preventDefault()
}}
>
{children}
</button>
)
}