diff --git a/CHANGELOG.md b/CHANGELOG.md index 567bc7de..0f74385d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/package-lock.json b/package-lock.json index 186c46d7..c50c8bde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 5d1541db..79468f0c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/InputAutocomplete.tsx b/src/components/InputAutocomplete.tsx index b0235cb3..79c28733 100644 --- a/src/components/InputAutocomplete.tsx +++ b/src/components/InputAutocomplete.tsx @@ -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 { - 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(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
{ - this.autocompleteMenuEl = el; - }} - > - 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) => ( -
- {item[1]} -
- )} + return ( +
+ +
+ {isOpen && + items.map((item, index) => ( +
+ {item[1]} +
+ ))} +
- } + ) }