Skip to content
Merged
11 changes: 11 additions & 0 deletions src/sidebar/search/AddressInput.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
scale: 0.7;
}

.btnCurrentLocation {
padding: 0 7px 0 5px;
color: grey;
width: 32px;
}

.btnCurrentLocation > svg {
height: 100%;
width: 100%;
}

.btnClose {
display: none;
}
Expand Down
82 changes: 42 additions & 40 deletions src/sidebar/search/AddressInput.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'
import { Coordinate, getBBoxFromCoord, QueryPoint, QueryPointType } from '@/stores/QueryStore'
import { Bbox, GeocodingHit, ReverseGeocodingHit } from '@/api/graphhopper'
import Autocomplete, {
AutocompleteItem,
GeocodingItem,
POIQueryItem,
SelectCurrentLocationItem,
} from '@/sidebar/search/AddressInputAutocomplete'
import Autocomplete, { AutocompleteItem, GeocodingItem, POIQueryItem } from '@/sidebar/search/AddressInputAutocomplete'

import ArrowBack from './arrow_back.svg'
import Cross from '@/sidebar/times-solid-thin.svg'
import CurrentLocationIcon from './current-location.svg'
import styles from './AddressInput.module.css'
import Api, { ApiImpl, getApi } from '@/api/Api'
import Api, { getApi } from '@/api/Api'
import { tr } from '@/translation/Translation'
import { coordinateToText, hitToItem, nominatimHitToItem, textToCoordinate } from '@/Converters'
import { useMediaQuery } from 'react-responsive'
import PopUp from '@/sidebar/search/PopUp'
import PlainButton from '@/PlainButton'
import { onCurrentLocationSelected } from '@/map/MapComponent'
import { toLonLat, transformExtent } from 'ol/proj'
import { calcDist } from '@/distUtils'
import { Map } from 'ol'
import { AddressParseResult } from '@/pois/AddressParseResult'
import { getMap } from '@/map/map'
Expand Down Expand Up @@ -79,17 +74,6 @@ export default function AddressInput(props: AddressInputProps) {

// if item is selected we need to clear the autocompletion list
useEffect(() => setAutocompleteItems([]), [props.point])
// if no items but input is selected show current location item
useEffect(() => {
if (hasFocus && text.length == 0 && autocompleteItems.length === 0)
setAutocompleteItems([new SelectCurrentLocationItem()])
}, [autocompleteItems, hasFocus])

function hideSuggestions() {
geocoder.cancel()
setOrigAutocompleteItems(autocompleteItems)
setAutocompleteItems([])
}

// highlighted result of geocoding results. Keep track which index is highlighted and change things on ArrowUp and Down
// on Enter select highlighted result or the 0th if nothing is highlighted
Expand All @@ -104,12 +88,9 @@ export default function AddressInput(props: AddressInputProps) {

const onKeypress = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
const inputElement = event.target as HTMLInputElement
if (event.key === 'Escape') {
// onBlur is deactivated for mobile so force:
setHasFocus(false)
setText(origText)
hideSuggestions()
searchInput.current!.blur()
return
}

Expand Down Expand Up @@ -146,7 +127,7 @@ export default function AddressInput(props: AddressInputProps) {
if (item instanceof POIQueryItem) {
handlePoiSearch(poiSearch, item.result, props.map)
props.onAddressSelected(item.result.text(item.result.poi), undefined)
} else if (highlightedResult < 0) {
} else if (highlightedResult < 0 && !props.point.isInitialized) {
// by default use the first result, otherwise the highlighted one
getApi()
.geocode(text, 'nominatim')
Expand All @@ -163,9 +144,8 @@ export default function AddressInput(props: AddressInputProps) {
props.onAddressSelected(item.toText(), item.point)
}
}
// onBlur is deactivated for mobile so force:
setHasFocus(false)
hideSuggestions()
// do not disturb 'tab' cycle
if (event.key == 'Enter') searchInput.current!.blur()
break
}
},
Expand All @@ -180,6 +160,9 @@ export default function AddressInput(props: AddressInputProps) {
const lonlat = toLonLat(getMap().getView().getCenter()!)
const biasCoord = { lng: lonlat[0], lat: lonlat[1] }

// do not focus on mobile as we would hide the map with the "input"-view
const focusFirstInput = props.index == 0 && !isSmallScreen

return (
<div className={containerClass}>
<div
Expand All @@ -196,17 +179,18 @@ export default function AddressInput(props: AddressInputProps) {
>
<PlainButton
className={styles.btnClose}
onClick={() => {
setHasFocus(false)
hideSuggestions()
}}
onMouseDown={
e => e.preventDefault() // prevents that input->onBlur is called when just "mouse down" event (lose focus only for onClick)
}
onClick={() => searchInput.current!.blur()}
>
<ArrowBack />
</PlainButton>
<input
style={props.moveStartIndex == props.index ? { borderWidth: '2px', margin: '-1px' } : {}}
className={styles.input}
type="text"
autoFocus={focusFirstInput}
ref={searchInput}
autoComplete="off"
onChange={e => {
Expand All @@ -223,7 +207,10 @@ export default function AddressInput(props: AddressInputProps) {
if (origAutocompleteItems.length > 0) setAutocompleteItems(origAutocompleteItems)
}}
onBlur={() => {
if (!isSmallScreen) hideSuggestions() // see #398
setHasFocus(false)
geocoder.cancel()
setOrigAutocompleteItems(autocompleteItems)
setAutocompleteItems([])
}}
value={text}
placeholder={tr(
Expand All @@ -232,17 +219,38 @@ export default function AddressInput(props: AddressInputProps) {
/>

<PlainButton
tabIndex={-1}
style={text.length == 0 ? { display: 'none' } : {}}
className={styles.btnInputClear}
onClick={() => {
onMouseDown={
e => e.preventDefault() // prevents that input->onBlur is called when clicking the button (would hide this button and prevent onClick)
}
onClick={e => {
setText('')
props.onChange('')
// if we clear the text without focus then explicitly request it to improve usability:
searchInput.current!.focus()
}}
>
<Cross />
</PlainButton>

<PlainButton
tabIndex={-1}
style={text.length == 0 && hasFocus ? {} : { display: 'none' }}
className={styles.btnCurrentLocation}
onMouseDown={
e => e.preventDefault() // prevents that input->onBlur is called when clicking the button (would hide this button and prevent onClick)
}
onClick={() => {
onCurrentLocationSelected(props.onAddressSelected)
// but when clicked => we want to lose the focus e.g. to close mobile-input view
searchInput.current!.blur()
}}
>
<CurrentLocationIcon />
</PlainButton>

{autocompleteItems.length > 0 && (
<ResponsiveAutocomplete
inputRef={searchInputContainer.current!}
Expand All @@ -253,19 +261,13 @@ export default function AddressInput(props: AddressInputProps) {
items={autocompleteItems}
highlightedItem={autocompleteItems[highlightedResult]}
onSelect={item => {
setHasFocus(false)
if (item instanceof GeocodingItem) {
hideSuggestions()
props.onAddressSelected(item.toText(), item.point)
} else if (item instanceof SelectCurrentLocationItem) {
hideSuggestions()
onCurrentLocationSelected(props.onAddressSelected)
} else if (item instanceof POIQueryItem) {
hideSuggestions()
handlePoiSearch(poiSearch, item.result, props.map)
setText(item.result.text(item.result.poi))
}
searchInput.current!.blur()
searchInput.current!.blur() // see also AutocompleteEntry->onMouseDown
}}
/>
</ResponsiveAutocomplete>
Expand Down
17 changes: 0 additions & 17 deletions src/sidebar/search/AddressInputAutocomplete.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,6 @@
background-color: #c6c6c6;
}

.currentLocationEntry {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
margin: 0.5rem 0.5rem;
}

.currentLocationIcon {
width: 1.2rem;
}

.currentLocationIcon > svg {
height: 100%;
width: 100%;
}

.poiEntry {
padding: 0.5em 0;
display: flex;
Expand Down
32 changes: 4 additions & 28 deletions src/sidebar/search/AddressInputAutocomplete.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import styles from './AddressInputAutocomplete.module.css'
import CurrentLocationIcon from './current-location.svg'
import { tr } from '@/translation/Translation'
import { Bbox } from '@/api/graphhopper'
import { AddressParseResult } from '@/pois/AddressParseResult'

Expand All @@ -24,8 +22,6 @@ export class GeocodingItem implements AutocompleteItem {
}
}

export class SelectCurrentLocationItem implements AutocompleteItem {}

export class POIQueryItem implements AutocompleteItem {
result: AddressParseResult

Expand Down Expand Up @@ -55,8 +51,6 @@ export default function Autocomplete({ items, highlightedItem, onSelect }: Autoc
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 SelectCurrentLocationItem)
return <SelectCurrentLocation 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)
Expand All @@ -82,27 +76,6 @@ export function POIQueryEntry({
)
}

export function SelectCurrentLocation({
item,
isHighlighted,
onSelect,
}: {
item: SelectCurrentLocationItem
isHighlighted: boolean
onSelect: (item: SelectCurrentLocationItem) => void
}) {
return (
<AutocompleteEntry isHighlighted={isHighlighted} onSelect={() => onSelect(item)}>
<div className={styles.currentLocationEntry}>
<div className={styles.currentLocationIcon}>
<CurrentLocationIcon />
</div>
<span className={styles.mainText}>{tr('current_location')}</span>
</div>
</AutocompleteEntry>
)
}

function GeocodingEntry({
item,
isHighlighted,
Expand Down Expand Up @@ -137,12 +110,15 @@ function AutocompleteEntry({
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 => {
e.preventDefault() // prevent blur event for our input, see #398
// 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}
Expand Down
9 changes: 0 additions & 9 deletions src/sidebar/search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,6 @@ const SearchBox = ({
}) => {
const point = points[index]

// With this ref and tabIndex=-1 we ensure that the first 'TAB' gives the focus the first input but the marker won't be included in the TAB sequence, #194
const myMarkerRef = useRef<HTMLDivElement>(null)

useEffect(() => {
if (index == 0) myMarkerRef.current?.focus()
}, [])

function onClickOrDrop() {
onDropPreviewSelect(-1)
const newIndex = moveStartIndex < index ? index + 1 : index
Expand All @@ -113,8 +106,6 @@ const SearchBox = ({
<>
{(moveStartIndex < 0 || moveStartIndex == index) && (
<div
ref={myMarkerRef}
tabIndex={-1}
title={tr('drag_to_reorder')}
className={styles.markerContainer}
draggable
Expand Down
Loading