mirror of
https://github.com/maputnik/editor.git
synced 2026-06-16 20:27:25 +00:00
Fix Downshift autocomplete behavior
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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])}
|
||||||
|
|||||||
@@ -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',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user