diff --git a/packages/select/src/common/listItemsProps.ts b/packages/select/src/common/listItemsProps.ts index 8cb45d133c..43cf53b951 100644 --- a/packages/select/src/common/listItemsProps.ts +++ b/packages/select/src/common/listItemsProps.ts @@ -112,6 +112,16 @@ export interface IListItemsProps extends IProps { */ resetOnSelect?: boolean; + /** + * When `activeItem` is controlled, whether the active item should _always_ + * be scrolled into view when the prop changes. If `false`, only changes + * that result from built-in interactions (clicking, querying, or using + * arrow keys) will scroll the active item into view. Ignored if the + * `activeItem` prop is omitted (uncontrolled behavior). + * @default true + */ + scrollToActiveItem?: boolean; + /** * Query string passed to `itemListPredicate` or `itemPredicate` to filter items. * This value is controlled: its state must be managed externally by attaching an `onChange` diff --git a/packages/select/src/components/query-list/queryList.tsx b/packages/select/src/components/query-list/queryList.tsx index 28618252a6..8be1e4506a 100644 --- a/packages/select/src/components/query-list/queryList.tsx +++ b/packages/select/src/components/query-list/queryList.tsx @@ -98,6 +98,13 @@ export class QueryList extends React.Component, IQueryList */ private shouldCheckActiveItemInViewport = false; + /** + * The item that we expect to be the next selected active item (based on click + * or key interactions). When scrollToActiveItem = false, used to detect if + * an unexpected external change to the active item has been made. + */ + private expectedNextActiveItem: T | null = null; + public constructor(props: IQueryListProps, context?: any) { super(props, context); const { query = "" } = this.props; @@ -152,6 +159,14 @@ export class QueryList extends React.Component, IQueryList } public scrollActiveItemIntoView() { + const scrollToActiveItem = this.props.scrollToActiveItem !== false; + const externalChangeToActiveItem = this.expectedNextActiveItem !== this.props.activeItem; + this.expectedNextActiveItem = null; + + if (!scrollToActiveItem && externalChangeToActiveItem) { + return; + } + const activeElement = this.getActiveElement(); if (this.itemsParentRef != null && activeElement != null) { const { offsetTop: activeTop, offsetHeight: activeHeight } = activeElement; @@ -294,6 +309,7 @@ export class QueryList extends React.Component, IQueryList } private setActiveItem(activeItem: T | null) { + this.expectedNextActiveItem = activeItem; if (this.props.activeItem === undefined) { // indicate that the active item may need to be scrolled into view after update. this.shouldCheckActiveItemInViewport = true;