diff --git a/src/components/autocomplete/AutoComplete.js b/src/components/autocomplete/AutoComplete.js index 07627c03ee..876f5fd707 100644 --- a/src/components/autocomplete/AutoComplete.js +++ b/src/components/autocomplete/AutoComplete.js @@ -28,6 +28,7 @@ export class AutoComplete extends Component { optionGroupTemplate: null, forceSelection: false, autoHighlight: false, + virtualScrollerOptions: null, scrollHeight: '200px', dropdown: false, dropdownMode: 'blank', @@ -86,6 +87,7 @@ export class AutoComplete extends Component { optionGroupTemplate: PropTypes.any, forceSelection: PropTypes.bool, autoHighlight: PropTypes.bool, + virtualScrollerOptions: PropTypes.object, scrollHeight: PropTypes.string, dropdown: PropTypes.bool, dropdownMode: PropTypes.string, @@ -160,6 +162,7 @@ export class AutoComplete extends Component { this.onPanelClick = this.onPanelClick.bind(this); this.overlayRef = createRef(); + this.virtualScrollerRef = createRef(); this.inputRef = createRef(this.props.inputRef); } @@ -377,7 +380,7 @@ export class AutoComplete extends Component { } } else { - highlightItem = this.overlayRef.current.firstChild.firstChild; + highlightItem = DomHandler.findSingle(this.overlayRef.current, 'li'); if (DomHandler.hasClass(highlightItem, 'p-autocomplete-item-group')) { highlightItem = this.findNextItem(highlightItem); } @@ -834,13 +837,9 @@ export class AutoComplete extends Component { {input} {loader} {dropdown} - <AutoCompletePanel ref={this.overlayRef} suggestions={this.props.suggestions} field={this.props.field} listId={this.state.id + '_list'} - appendTo={this.props.appendTo} scrollHeight={this.props.scrollHeight} itemTemplate={this.props.itemTemplate} onItemClick={this.selectItem} ariaSelected={this.ariaSelected} - panelStyle={this.props.panelStyle} panelClassName={this.props.panelClassName} onClick={this.onPanelClick} - optionGroupLabel={this.props.optionGroupLabel} optionGroupChildren={this.props.optionGroupChildren} optionGroupTemplate={this.props.optionGroupTemplate} - getOptionGroupLabel={this.getOptionGroupLabel} getOptionGroupChildren={this.getOptionGroupChildren} - in={this.state.overlayVisible} onEnter={this.onOverlayEnter} onEntering={this.onOverlayEntering} onEntered={this.onOverlayEntered} onExit={this.onOverlayExit} onExited={this.onOverlayExited} - transitionOptions={this.props.transitionOptions} /> + <AutoCompletePanel ref={this.overlayRef} virtualScrollerRef={this.virtualScrollerRef} {...this.props} listId={this.state.id + '_list'} onItemClick={this.selectItem} ariaSelected={this.ariaSelected} + onClick={this.onPanelClick} getOptionGroupLabel={this.getOptionGroupLabel} getOptionGroupChildren={this.getOptionGroupChildren} + in={this.state.overlayVisible} onEnter={this.onOverlayEnter} onEntering={this.onOverlayEntering} onEntered={this.onOverlayEntered} onExit={this.onOverlayExit} onExited={this.onOverlayExited} /> </span> ); } diff --git a/src/components/autocomplete/AutoCompletePanel.js b/src/components/autocomplete/AutoCompletePanel.js index 021d6685cf..a9e7940a55 100644 --- a/src/components/autocomplete/AutoCompletePanel.js +++ b/src/components/autocomplete/AutoCompletePanel.js @@ -1,53 +1,13 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import ObjectUtils from '../utils/ObjectUtils'; import { Ripple } from '../ripple/Ripple'; import { classNames } from '../utils/ClassNames'; import { CSSTransition } from '../transition/CSSTransition'; import { Portal } from '../portal/Portal'; +import { VirtualScroller } from '../virtualscroller/VirtualScroller'; class AutoCompletePanelComponent extends Component { - static defaultProps = { - suggestions: null, - field: null, - appendTo: null, - optionGroupLabel: null, - optionGroupChildren: null, - optionGroupTemplate: null, - itemTemplate: null, - onItemClick: null, - scrollHeight: '200px', - listId: null, - ariaSelected: null, - panelClassName: null, - panelStyle: null, - forwardRef: null, - onClick: null, - getOptionGroupLabel: null, - getOptionGroupChildren: null - } - - static propTypes = { - suggestions: PropTypes.array, - field: PropTypes.string, - appendTo: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - optionGroupLabel: PropTypes.string, - optionGroupChildren: PropTypes.string, - optionGroupTemplate: PropTypes.any, - itemTemplate: PropTypes.any, - onItemClick: PropTypes.func, - scrollHeight: PropTypes.string, - listId: PropTypes.any, - ariaSelected: PropTypes.any, - panelClassName: PropTypes.string, - panelStyle: PropTypes.object, - forwardRef: PropTypes.any, - onClick: PropTypes.func, - getOptionGroupLabel: PropTypes.func, - getOptionGroupChildren: PropTypes.func - }; - getOptionGroupRenderKey(optionGroup) { return ObjectUtils.resolveFieldData(optionGroup, this.props.optionGroupLabel); } @@ -68,53 +28,81 @@ class AutoCompletePanelComponent extends Component { ) } + renderItem(suggestion, index) { + if (this.props.optionGroupLabel) { + const groupContent = this.props.optionGroupTemplate ? ObjectUtils.getJSXElement(this.props.optionGroupTemplate, suggestion, index) : this.props.getOptionGroupLabel(suggestion); + const groupChildrenContent = this.renderGroupChildren(suggestion, index); + const key = index + '_' + this.getOptionGroupRenderKey(suggestion); + + return ( + <React.Fragment key={key}> + <li className="p-autocomplete-item-group"> + {groupContent} + </li> + {groupChildrenContent} + </React.Fragment> + ) + } + else { + let itemContent = this.props.itemTemplate ? ObjectUtils.getJSXElement(this.props.itemTemplate, suggestion, index) : this.props.field ? ObjectUtils.resolveFieldData(suggestion, this.props.field) : suggestion; + + return ( + <li key={index + '_item'} role="option" aria-selected={this.props.ariaSelected === suggestion} className="p-autocomplete-item" onClick={(e) => this.props.onItemClick(e, suggestion)}> + {itemContent} + <Ripple /> + </li> + ); + } + } + renderItems() { if (this.props.suggestions) { - if (this.props.optionGroupLabel) { - return this.props.suggestions.map((suggestion, i) => { - const groupContent = this.props.optionGroupTemplate ? ObjectUtils.getJSXElement(this.props.optionGroupTemplate, suggestion, i) : this.props.getOptionGroupLabel(suggestion); - const groupChildrenContent = this.renderGroupChildren(suggestion, i); - const key = i + '_' + this.getOptionGroupRenderKey(suggestion); + return this.props.suggestions.map((suggestion, index) => this.renderItem(suggestion, index)); + } - return ( - <React.Fragment key={key}> - <li className="p-autocomplete-item-group"> - {groupContent} - </li> - {groupChildrenContent} - </React.Fragment> - ) - }); - } - else { - return this.props.suggestions.map((suggestion, index) => { - let itemContent = this.props.itemTemplate ? ObjectUtils.getJSXElement(this.props.itemTemplate, suggestion, index) : this.props.field ? ObjectUtils.resolveFieldData(suggestion, this.props.field) : suggestion; + return null; + } + + renderContent() { + if (this.props.virtualScrollerOptions) { + const virtualScrollerProps = { ...this.props.virtualScrollerOptions, ...{ + style: {...this.props.virtualScrollerOptions.style, ...{ height: this.props.scrollHeight || 'auto' }}, + items: this.props.suggestions, + itemTemplate: (item, options) => item && this.renderItem(item, options.index), + contentTemplate: (options) => { + const className = classNames('p-autocomplete-items', options.className); return ( - <li key={index + '_item'} role="option" aria-selected={this.props.ariaSelected === suggestion} className="p-autocomplete-item" onClick={(e) => this.props.onItemClick(e, suggestion)}> - {itemContent} - <Ripple /> - </li> + <ul ref={options.ref} className={className} role="listbox" id={this.props.listId}> + {options.children} + </ul> ); - }); - } + } + }}; + + return <VirtualScroller ref={this.props.virtualScrollerRef} {...virtualScrollerProps} />; } + else { + const items = this.renderItems(); - return null; + return ( + <ul className="p-autocomplete-items" role="listbox" id={this.props.listId}> + {items} + </ul> + ); + } } renderElement() { const panelClassName = classNames('p-autocomplete-panel p-component', this.props.panelClassName); const panelStyle = { maxHeight: this.props.scrollHeight, ...this.props.panelStyle }; - let items = this.renderItems(); + const content = this.renderContent(); return ( <CSSTransition nodeRef={this.props.forwardRef} classNames="p-connected-overlay" in={this.props.in} timeout={{ enter: 120, exit: 100 }} options={this.props.transitionOptions} unmountOnExit onEnter={this.props.onEnter} onEntering={this.props.onEntering} onEntered={this.props.onEntered} onExit={this.props.onExit} onExited={this.props.onExited}> <div ref={this.props.forwardRef} className={panelClassName} style={panelStyle} onClick={this.props.onClick}> - <ul className="p-autocomplete-items" role="listbox" id={this.props.listId} style={{ maxHeight: this.props.scrollHeight || 'auto' }}> - {items} - </ul> + {content} </div> </CSSTransition> );