diff --git a/package.json b/package.json index 6647eefa60e..6d92558a388 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "@types/numeral": "^0.0.25", "@types/react-beautiful-dnd": "^12.1.2", "@types/react-input-autosize": "^2.0.2", - "@types/react-virtualized": "^9.18.7", "@types/react-virtualized-auto-sizer": "^1.0.0", "@types/react-window": "^1.8.1", "chroma-js": "^2.0.4", @@ -68,7 +67,6 @@ "react-focus-lock": "^1.17.7", "react-input-autosize": "^2.2.2", "react-is": "~16.3.0", - "react-virtualized": "^9.21.2", "react-virtualized-auto-sizer": "^1.0.2", "react-window": "^1.8.5", "resize-observer-polyfill": "^1.5.0", diff --git a/src-docs/src/views/combo_box/combo_box_example.js b/src-docs/src/views/combo_box/combo_box_example.js index 2d2fb9fe41f..19e0a1756a5 100644 --- a/src-docs/src/views/combo_box/combo_box_example.js +++ b/src-docs/src/views/combo_box/combo_box_example.js @@ -147,8 +147,8 @@ export const ComboBoxExample = { text: (

EuiComboBoxList uses{' '} - - react-virtualized + + react-window {' '} to only render visible options to be super fast no matter how many options there are. diff --git a/src/components/combo_box/__snapshots__/combo_box.test.tsx.snap b/src/components/combo_box/__snapshots__/combo_box.test.tsx.snap index 6d6cedcf6f5..458d4780f91 100644 --- a/src/components/combo_box/__snapshots__/combo_box.test.tsx.snap +++ b/src/components/combo_box/__snapshots__/combo_box.test.tsx.snap @@ -316,14 +316,224 @@ exports[`props options list is rendered 1`] = ` class="euiComboBoxOptionsList__rowWrap" >

+ style="position: relative; height: 189px; width: 0px; overflow: auto; direction: ltr;" + > +
+ + + + + + + + + +
+
diff --git a/src/components/combo_box/combo_box.tsx b/src/components/combo_box/combo_box.tsx index ebad2f90cd5..0c368746020 100644 --- a/src/components/combo_box/combo_box.tsx +++ b/src/components/combo_box/combo_box.tsx @@ -534,6 +534,7 @@ export class EuiComboBox extends Component< relatedTarget && this.comboBoxRefInstance && this.comboBoxRefInstance.contains(relatedTarget); + if (!focusedInOptionsList && !focusedInInput) { this.closeList(); @@ -652,6 +653,10 @@ export class EuiComboBox extends Component< if (singleSelection) { requestAnimationFrame(this.closeList); + } else { + this.setState({ + activeOptionIndex: this.state.matchingOptions.indexOf(addedOption), + }); } }; diff --git a/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx b/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx index 8d230a1e82b..e6d278914cb 100644 --- a/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx +++ b/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx @@ -5,7 +5,11 @@ import React, { RefCallback, } from 'react'; import classNames from 'classnames'; -import { List, ListProps } from 'react-virtualized'; // eslint-disable-line import/named +import { + FixedSizeList, + ListProps, + ListChildComponentProps, +} from 'react-window'; import { EuiCode } from '../../../components/code'; import { EuiFlexGroup, EuiFlexItem } from '../../flex'; @@ -83,6 +87,8 @@ export class EuiComboBoxOptionsList extends Component< EuiComboBoxOptionsListProps > { listRefInstance: RefInstance = null; + listRef: FixedSizeList | null = null; + listBoxRef: HTMLUListElement | null = null; static defaultProps = { 'data-test-subj': '', @@ -126,6 +132,10 @@ export class EuiComboBoxOptionsList extends Component< ) { this.updatePosition(); } + + if (this.listRef && typeof this.props.activeOptionIndex !== 'undefined') { + this.listRef.scrollToItem(this.props.activeOptionIndex, 'auto'); + } } componentWillUnmount() { @@ -153,6 +163,80 @@ export class EuiComboBoxOptionsList extends Component< this.listRefInstance = ref; }; + setListRef = (ref: FixedSizeList | null) => { + this.listRef = ref; + }; + + setListBoxRef = (ref: HTMLUListElement | null) => { + this.listBoxRef = ref; + + if (ref) { + ref.setAttribute('id', this.props.rootId('listbox')); + ref.setAttribute('role', 'listBox'); + ref.setAttribute('tabIndex', '0'); + } + }; + + ListRow = ({ data, index, style }: ListChildComponentProps) => { + const option = data[index]; + const { isGroupLabelOption, label, value, ...rest } = option; + const { + singleSelection, + selectedOptions, + onOptionClick, + optionRef, + activeOptionIndex, + renderOption, + searchValue, + rootId, + } = this.props; + + if (isGroupLabelOption) { + return ( +
+ {label} +
+ ); + } + + let checked: FilterChecked | undefined = undefined; + if ( + singleSelection && + selectedOptions.length && + selectedOptions[0].label === label + ) { + checked = 'on'; + } + + return ( + { + if (onOptionClick) { + onOptionClick(option); + } + }} + ref={optionRef.bind(this, index)} + isFocused={activeOptionIndex === index} + checked={checked} + showIcons={singleSelection ? true : false} + id={rootId(`_option-${index}`)} + title={label} + {...rest}> + {renderOption ? ( + renderOption(option, searchValue, OPTION_CONTENT_CLASSNAME) + ) : ( + + {label} + + )} + + ); + }; + render() { const { 'data-test-subj': dataTestSubj, @@ -289,65 +373,17 @@ export class EuiComboBoxOptionsList extends Component< const height = numVisibleOptions * rowHeight; const optionsList = ( - { - const option = matchingOptions[index]; - const { isGroupLabelOption, label, value, ...rest } = option; - - if (isGroupLabelOption) { - return ( -
- {label} -
- ); - } - - let checked: FilterChecked | undefined = undefined; - if ( - singleSelection && - selectedOptions.length && - selectedOptions[0].label === label - ) { - checked = 'on'; - } - - return ( - { - if (onOptionClick) { - onOptionClick(option); - } - }} - ref={optionRef.bind(this, index)} - isFocused={activeOptionIndex === index} - checked={checked} - showIcons={singleSelection ? true : false} - id={rootId(`_option-${index}`)} - title={label} - {...rest}> - {renderOption ? ( - renderOption(option, searchValue, OPTION_CONTENT_CLASSNAME) - ) : ( - - {label} - - )} - - ); - }} - role="listbox" - rowCount={matchingOptions.length} - rowHeight={rowHeight} - scrollToIndex={scrollToIndex} - width={width} - /> + itemCount={matchingOptions.length} + itemSize={rowHeight} + itemData={matchingOptions} + ref={this.setListRef} + innerRef={this.setListBoxRef} + width={width}> + {this.ListRow} + ); const classes = classNames( diff --git a/yarn.lock b/yarn.lock index c4aadbabcb7..f2ccf20c97e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1303,14 +1303,6 @@ dependencies: "@types/react" "*" -"@types/react-virtualized@^9.18.7": - version "9.21.8" - resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.21.8.tgz#dc0150a75fd6e42f33729886463ece04d03367ea" - integrity sha512-7fZoA0Azd2jLIE9XC37fMZgMqaJe3o3pfzGjvrzphoKjBCdT4oNl6wikvo4dDMESDnpkZ8DvVTc7aSe4DW86Ew== - dependencies: - "@types/prop-types" "*" - "@types/react" "*" - "@types/react-window@^1.8.1": version "1.8.1" resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.1.tgz#6e1ceab2e6f2f78dbf1f774ee0e00f1bb0364bb3" @@ -3358,11 +3350,6 @@ cloneable-readable@^1.0.0: process-nextick-args "^1.0.6" through2 "^2.0.1" -clsx@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702" - integrity sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA== - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -4210,7 +4197,7 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": dependencies: cssom "0.3.x" -csstype@^2.2.0, csstype@^2.6.7: +csstype@^2.2.0: version "2.6.9" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== @@ -4672,14 +4659,6 @@ dom-converter@~0.1: dependencies: utila "~0.3" -dom-helpers@^5.0.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.3.tgz#7233248eb3a2d1f74aafca31e52c5299cc8ce821" - integrity sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw== - dependencies: - "@babel/runtime" "^7.6.3" - csstype "^2.6.7" - dom-serializer@0, dom-serializer@^0.1.0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -9137,7 +9116,7 @@ loglevel@^1.4.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= -loose-envify@^1.0.0, loose-envify@^1.3.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -12208,7 +12187,7 @@ react-konva@16.10.1-0: react-reconciler "^0.22.1" scheduler "^0.16.1" -react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: +react-lifecycles-compat@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== @@ -12310,18 +12289,6 @@ react-virtualized-auto-sizer@^1.0.2: resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd" integrity sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg== -react-virtualized@^9.21.2: - version "9.21.2" - resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.2.tgz#02e6df65c1e020c8dbf574ec4ce971652afca84e" - integrity sha512-oX7I7KYiUM7lVXQzmhtF4Xg/4UA5duSA+/ZcAvdWlTLFCoFYq1SbauJT5gZK9cZS/wdYR6TPGpX/dqzvTqQeBA== - dependencies: - babel-runtime "^6.26.0" - clsx "^1.0.1" - dom-helpers "^5.0.0" - loose-envify "^1.3.0" - prop-types "^15.6.0" - react-lifecycles-compat "^3.0.4" - react-window@^1.8.5: version "1.8.5" resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1"