Fix Downshift autocomplete behavior

This commit is contained in:
Bart Louwers
2025-07-05 02:11:41 +02:00
parent efe9feb9f5
commit 3df4576cee
3 changed files with 9 additions and 34 deletions
+1
View File
@@ -15,6 +15,7 @@
- Remove react-autobind dependency - Remove react-autobind dependency
- Remove usage of legacy `childContextTypes` API - Remove usage of legacy `childContextTypes` API
- Replace react-autocomplete with Downshift in the autocomplete component - Replace react-autocomplete with Downshift in the autocomplete component
- Simplify autocomplete component and fix dropdown flicker when clicking
- _...Add new stuff here..._ - _...Add new stuff here..._
### 🐞 Bug fixes ### 🐞 Bug fixes
-1
View File
@@ -29,7 +29,6 @@ class FieldSourceLayerInternal extends React.Component<FieldSourceLayerInternalP
error={this.props.error} error={this.props.error}
> >
<InputAutocomplete <InputAutocomplete
keepMenuWithinWindowBounds={!!this.props.isFixed}
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}
options={this.props.sourceLayerIds?.map(l => [l, l])} options={this.props.sourceLayerIds?.map(l => [l, l])}
+8 -33
View File
@@ -2,14 +2,10 @@ import React from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import {useCombobox} from 'downshift' import {useCombobox} from 'downshift'
const MAX_HEIGHT = 140;
export type InputAutocompleteProps = { export type InputAutocompleteProps = {
value?: string value?: string
options?: any[] options?: any[]
onChange?(value: string | undefined): unknown onChange?(value: string | undefined): unknown
keepMenuWithinWindowBounds?: boolean
'aria-label'?: string 'aria-label'?: string
}; };
@@ -17,12 +13,9 @@ export default function InputAutocomplete({
value, value,
options = [], options = [],
onChange = () => {}, onChange = () => {},
keepMenuWithinWindowBounds,
'aria-label': ariaLabel, 'aria-label': ariaLabel,
}: InputAutocompleteProps) { }: InputAutocompleteProps) {
const [maxHeight, setMaxHeight] = React.useState(MAX_HEIGHT)
const [input, setInput] = React.useState(value || '') const [input, setInput] = React.useState(value || '')
const autocompleteMenuEl = React.useRef<HTMLDivElement | null>(null)
const filteredItems = React.useMemo(() => { const filteredItems = React.useMemo(() => {
const lv = input.toLowerCase() const lv = input.toLowerCase()
@@ -40,18 +33,21 @@ export default function InputAutocomplete({
items: filteredItems, items: filteredItems,
inputValue: input, inputValue: input,
itemToString: (item) => (item ? item[0] : ''), itemToString: (item) => (item ? item[0] : ''),
stateReducer: (_state, action) => {
if (action.type === useCombobox.stateChangeTypes.InputClick) {
return {...action.changes, isOpen: true}
}
return action.changes
},
onSelectedItemChange: ({selectedItem}) => { onSelectedItemChange: ({selectedItem}) => {
const v = selectedItem ? selectedItem[0] : '' const v = selectedItem ? selectedItem[0] : ''
setInput(v) setInput(v)
onChange(selectedItem ? selectedItem[0] : undefined) onChange(selectedItem ? selectedItem[0] : undefined)
}, },
onInputValueChange: ({inputValue: v, type}) => { onInputValueChange: ({inputValue: v}) => {
if (typeof v === 'string') { if (typeof v === 'string') {
setInput(v) setInput(v)
onChange(v === '' ? undefined : v) onChange(v === '' ? undefined : v)
if (type === useCombobox.stateChangeTypes.InputChange) {
openMenu()
}
} }
}, },
}) })
@@ -60,23 +56,8 @@ export default function InputAutocomplete({
setInput(value || '') setInput(value || '')
}, [value]) }, [value])
const calcMaxHeight = React.useCallback(() => {
if (keepMenuWithinWindowBounds && autocompleteMenuEl.current) {
const maxHeightLocal = window.innerHeight -
autocompleteMenuEl.current.getBoundingClientRect().top
const limitedMaxHeight = Math.min(maxHeightLocal, MAX_HEIGHT)
if (limitedMaxHeight !== maxHeight) {
setMaxHeight(limitedMaxHeight)
}
}
}, [keepMenuWithinWindowBounds, maxHeight])
React.useEffect(() => {
calcMaxHeight()
})
return ( return (
<div ref={autocompleteMenuEl} className="maputnik-autocomplete"> <div className="maputnik-autocomplete">
<input <input
{...getInputProps({ {...getInputProps({
'aria-label': ariaLabel, 'aria-label': ariaLabel,
@@ -87,12 +68,6 @@ export default function InputAutocomplete({
/> />
<div <div
{...getMenuProps({ {...getMenuProps({
style: {
position: 'fixed',
overflow: 'auto',
maxHeight: maxHeight,
zIndex: '998',
},
className: 'maputnik-autocomplete-menu', className: 'maputnik-autocomplete-menu',
})} })}
> >