mirror of
https://github.com/maputnik/editor.git
synced 2026-02-11 15:10:01 +00:00
chore: replace autocomplete lib
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
- When loading a style into localStorage that causes a QuotaExceededError, purge localStorage and retry
|
||||
- Remove react-autobind dependency
|
||||
- Remove usage of legacy `childContextTypes` API
|
||||
- Replace react-autosuggest with Downshift in the autocomplete component
|
||||
- _...Add new stuff here..._
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
62
package-lock.json
generated
62
package-lock.json
generated
@@ -22,6 +22,7 @@
|
||||
"codemirror": "^5.65.19",
|
||||
"color": "^5.0.0",
|
||||
"detect-browser": "^5.3.0",
|
||||
"downshift": "^9.0.9",
|
||||
"events": "^3.3.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^25.3.1",
|
||||
@@ -46,7 +47,6 @@
|
||||
"react-accessible-accordion": "^5.0.1",
|
||||
"react-aria-menubutton": "^7.0.3",
|
||||
"react-aria-modal": "^5.0.2",
|
||||
"react-autocomplete": "^1.8.1",
|
||||
"react-collapse": "^5.1.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -82,7 +82,6 @@
|
||||
"@types/react": "^18.2.67",
|
||||
"@types/react-aria-menubutton": "^6.2.14",
|
||||
"@types/react-aria-modal": "^5.0.0",
|
||||
"@types/react-autocomplete": "^1.8.11",
|
||||
"@types/react-collapse": "^5.0.4",
|
||||
"@types/react-color": "^3.0.13",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
@@ -3114,16 +3113,6 @@
|
||||
"focus-trap": "^7.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-autocomplete": {
|
||||
"version": "1.8.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-autocomplete/-/react-autocomplete-1.8.11.tgz",
|
||||
"integrity": "sha512-JYaD/OGfVFMK5NaGOCAd25QRs4MEevapn38xvYWjwo5brxrUMo2PucYmShTfuTX99r80UtncCMDrFZ9MGIVyvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-collapse": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-collapse/-/react-collapse-5.0.4.tgz",
|
||||
@@ -4690,6 +4679,12 @@
|
||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
|
||||
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -5211,11 +5206,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-scroll-into-view": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-1.0.1.tgz",
|
||||
"integrity": "sha512-1Dmy6uH1vRcm2+Lvggyrlc04cMh+mr+VA+qcgs085hAEZp+v+6NT/xhRjfc6vRc7965sCSDdQcw063VkG+eNmQ=="
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
@@ -5281,6 +5271,28 @@
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/downshift": {
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/downshift/-/downshift-9.0.9.tgz",
|
||||
"integrity": "sha512-ygOT8blgiz5liDuEFAIaPeU4dDEa+w9p6PHVUisPIjrkF5wfR59a52HpGWAVVMoWnoFO8po2mZSScKZueihS7g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.5",
|
||||
"compute-scroll-into-view": "^3.1.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "18.2.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/downshift/node_modules/react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -10771,19 +10783,6 @@
|
||||
"react-dom": ">=16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-autocomplete": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-autocomplete/-/react-autocomplete-1.8.1.tgz",
|
||||
"integrity": "sha512-YQGVN5POdcI3G89wUVWnJhk9rLF6JeB6Ik6xnNpfvSMG4tJkksBzqOE4mkFNGqEz+2AaQw13xNmVXresg9E3zg==",
|
||||
"dependencies": {
|
||||
"dom-scroll-into-view": "1.0.1",
|
||||
"prop-types": "^15.5.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.7 || ^15.0.0-0 || ^16.0.0-0",
|
||||
"react-dom": "^0.14.7 || ^15.0.0-0 || ^16.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-collapse": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-collapse/-/react-collapse-5.1.1.tgz",
|
||||
@@ -12662,8 +12661,7 @@
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"react-accessible-accordion": "^5.0.1",
|
||||
"react-aria-menubutton": "^7.0.3",
|
||||
"react-aria-modal": "^5.0.2",
|
||||
"react-autocomplete": "^1.8.1",
|
||||
"downshift": "^9.0.9",
|
||||
"react-collapse": "^5.1.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -113,7 +113,6 @@
|
||||
"@types/react": "^18.2.67",
|
||||
"@types/react-aria-menubutton": "^6.2.14",
|
||||
"@types/react-aria-modal": "^5.0.0",
|
||||
"@types/react-autocomplete": "^1.8.11",
|
||||
"@types/react-collapse": "^5.0.4",
|
||||
"@types/react-color": "^3.0.13",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
|
||||
@@ -1,100 +1,102 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
import Autocomplete from 'react-autocomplete'
|
||||
import {useCombobox} from 'downshift'
|
||||
|
||||
|
||||
const MAX_HEIGHT = 140;
|
||||
|
||||
export type InputAutocompleteProps = {
|
||||
value?: string
|
||||
options: any[]
|
||||
onChange(value: string | undefined): unknown
|
||||
options?: any[]
|
||||
onChange?(value: string | undefined): unknown
|
||||
keepMenuWithinWindowBounds?: boolean
|
||||
'aria-label'?: string
|
||||
};
|
||||
|
||||
export default class InputAutocomplete extends React.Component<InputAutocompleteProps> {
|
||||
state = {
|
||||
maxHeight: MAX_HEIGHT
|
||||
}
|
||||
export default function InputAutocomplete({
|
||||
value,
|
||||
options = [],
|
||||
onChange = () => {},
|
||||
keepMenuWithinWindowBounds,
|
||||
'aria-label': ariaLabel,
|
||||
}: InputAutocompleteProps) {
|
||||
const [maxHeight, setMaxHeight] = React.useState(MAX_HEIGHT)
|
||||
const autocompleteMenuEl = React.useRef<HTMLDivElement | null>(null)
|
||||
|
||||
autocompleteMenuEl: HTMLDivElement | null = null;
|
||||
const items = options
|
||||
|
||||
static defaultProps = {
|
||||
onChange: () => {},
|
||||
options: [],
|
||||
}
|
||||
const {
|
||||
isOpen,
|
||||
getMenuProps,
|
||||
getInputProps,
|
||||
getItemProps,
|
||||
highlightedIndex,
|
||||
inputValue,
|
||||
} = useCombobox({
|
||||
items,
|
||||
inputValue: value,
|
||||
itemToString: (item) => (item ? item[0] : ''),
|
||||
onSelectedItemChange: ({selectedItem}) => {
|
||||
onChange(selectedItem ? selectedItem[0] : undefined)
|
||||
},
|
||||
onInputValueChange: ({inputValue: v}) => {
|
||||
if (v !== undefined && v !== inputValue) {
|
||||
onChange(v === '' ? undefined : v)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
calcMaxHeight() {
|
||||
if(this.props.keepMenuWithinWindowBounds) {
|
||||
const maxHeight = window.innerHeight - this.autocompleteMenuEl!.getBoundingClientRect().top;
|
||||
const limitedMaxHeight = Math.min(maxHeight, MAX_HEIGHT);
|
||||
|
||||
if(limitedMaxHeight != this.state.maxHeight) {
|
||||
this.setState({
|
||||
maxHeight: limitedMaxHeight
|
||||
})
|
||||
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])
|
||||
|
||||
componentDidMount() {
|
||||
this.calcMaxHeight();
|
||||
}
|
||||
React.useEffect(() => {
|
||||
calcMaxHeight()
|
||||
})
|
||||
|
||||
componentDidUpdate() {
|
||||
this.calcMaxHeight();
|
||||
}
|
||||
|
||||
onChange(v: string) {
|
||||
this.props.onChange(v === "" ? undefined : v);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div
|
||||
ref={(el) => {
|
||||
this.autocompleteMenuEl = el;
|
||||
}}
|
||||
>
|
||||
<Autocomplete
|
||||
menuStyle={{
|
||||
position: "fixed",
|
||||
overflow: "auto",
|
||||
maxHeight: this.state.maxHeight,
|
||||
zIndex: '998'
|
||||
}}
|
||||
wrapperProps={{
|
||||
className: "maputnik-autocomplete",
|
||||
style: {}
|
||||
}}
|
||||
inputProps={{
|
||||
'aria-label': this.props['aria-label'],
|
||||
className: "maputnik-string",
|
||||
spellCheck: false
|
||||
}}
|
||||
value={this.props.value}
|
||||
items={this.props.options}
|
||||
getItemValue={(item) => item[0]}
|
||||
onSelect={v => this.onChange(v)}
|
||||
onChange={(_e, v) => this.onChange(v)}
|
||||
shouldItemRender={(item, value="") => {
|
||||
if (typeof(value) === "string") {
|
||||
return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
|
||||
}
|
||||
return false
|
||||
}}
|
||||
renderItem={(item, isHighlighted) => (
|
||||
<div
|
||||
key={item[0]}
|
||||
className={classnames({
|
||||
"maputnik-autocomplete-menu-item": true,
|
||||
"maputnik-autocomplete-menu-item-selected": isHighlighted,
|
||||
})}
|
||||
>
|
||||
{item[1]}
|
||||
</div>
|
||||
)}
|
||||
return (
|
||||
<div ref={autocompleteMenuEl} className="maputnik-autocomplete">
|
||||
<input
|
||||
{...getInputProps({
|
||||
'aria-label': ariaLabel,
|
||||
className: 'maputnik-string',
|
||||
spellCheck: false,
|
||||
})}
|
||||
/>
|
||||
<div
|
||||
{...getMenuProps({
|
||||
style: {
|
||||
position: 'fixed',
|
||||
overflow: 'auto',
|
||||
maxHeight: maxHeight,
|
||||
zIndex: '998',
|
||||
},
|
||||
className: 'maputnik-autocomplete-menu',
|
||||
})}
|
||||
>
|
||||
{isOpen &&
|
||||
items.map((item, index) => (
|
||||
<div
|
||||
key={item[0]}
|
||||
{...getItemProps({
|
||||
item,
|
||||
index,
|
||||
className: classnames('maputnik-autocomplete-menu-item', {
|
||||
'maputnik-autocomplete-menu-item-selected': highlightedIndex === index,
|
||||
}),
|
||||
})}
|
||||
>
|
||||
{item[1]}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user