Commit 34f61934 by David Committed by GitHub

Editor: No default suggestion selected (#24479)

* QueryField: No default suggestion selected

It's been a long-standing issue that careless typing lead to unwanted
tab completion insertions. With this change the completion item list no
longer selects the first item by default. The user has to actively click
ArrowDown to select the first one.

* Added type export

* Remove width limit of typeahead list
parent b16202ac
import React from 'react';
import { mount } from 'enzyme';
import { Typeahead, State } from './Typeahead';
import { TypeaheadItem } from './TypeaheadItem';
import { CompletionItemKind } from '../../types';
describe('Typeahead', () => {
const completionItemGroups = [{ label: 'my group', items: [{ label: 'first item' }] }];
describe('when closed', () => {
it('renders nothing when no items given', () => {
const component = mount(<Typeahead origin="test" groupedItems={[]} />);
expect(component.find('.typeahead')).toHaveLength(0);
});
it('renders nothing when items given', () => {
const component = mount(<Typeahead origin="test" groupedItems={completionItemGroups} />);
expect(component.find('.typeahead')).toHaveLength(0);
});
});
describe('when open', () => {
it('renders given items and nothing is selected', () => {
const component = mount(<Typeahead origin="test" groupedItems={completionItemGroups} isOpen />);
expect(component.find('.typeahead')).toHaveLength(1);
const items = component.find(TypeaheadItem);
expect(items).toHaveLength(2);
expect(items.get(0).props.item.kind).toEqual(CompletionItemKind.GroupTitle);
expect(items.get(0).props.isSelected).toBeFalsy();
expect(items.get(1).props.item.label).toEqual('first item');
expect(items.get(1).props.isSelected).toBeFalsy();
});
});
it('selected the first non-group item on moving to first item', () => {
const component = mount(<Typeahead origin="test" groupedItems={completionItemGroups} isOpen />);
expect(component.find('.typeahead')).toHaveLength(1);
let items = component.find(TypeaheadItem);
expect(items).toHaveLength(2);
expect((component.state() as State).typeaheadIndex).toBe(null);
(component.instance() as Typeahead).moveMenuIndex(1);
expect((component.state() as State).typeaheadIndex).toBe(1);
component.setProps({});
items = component.find(TypeaheadItem);
expect(items.get(0).props.isSelected).toBeFalsy();
expect(items.get(1).props.isSelected).toBeTruthy();
});
});
......@@ -20,13 +20,13 @@ interface Props {
isOpen?: boolean;
}
interface State {
export interface State {
allItems: CompletionItem[];
listWidth: number;
listHeight: number;
itemHeight: number;
hoveredItem: number | null;
typeaheadIndex: number;
typeaheadIndex: number | null;
}
export class Typeahead extends React.PureComponent<Props, State> {
......@@ -34,7 +34,14 @@ export class Typeahead extends React.PureComponent<Props, State> {
context!: React.ContextType<typeof ThemeContext>;
listRef = createRef<FixedSizeList>();
state: State = { hoveredItem: null, typeaheadIndex: 1, allItems: [], listWidth: -1, listHeight: -1, itemHeight: -1 };
state: State = {
hoveredItem: null,
typeaheadIndex: null,
allItems: [],
listWidth: -1,
listHeight: -1,
itemHeight: -1,
};
componentDidMount = () => {
if (this.props.menuRef) {
......@@ -63,7 +70,12 @@ export class Typeahead extends React.PureComponent<Props, State> {
};
componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>) => {
if (prevState.typeaheadIndex !== this.state.typeaheadIndex && this.listRef && this.listRef.current) {
if (
this.state.typeaheadIndex !== null &&
prevState.typeaheadIndex !== this.state.typeaheadIndex &&
this.listRef &&
this.listRef.current
) {
if (this.state.typeaheadIndex === 1) {
this.listRef.current.scrollToItem(0); // special case for handling the first group label
return;
......@@ -75,7 +87,7 @@ export class Typeahead extends React.PureComponent<Props, State> {
const allItems = flattenGroupItems(this.props.groupedItems);
const longestLabel = calculateLongestLabel(allItems);
const { listWidth, listHeight, itemHeight } = calculateListSizes(this.context, allItems, longestLabel);
this.setState({ listWidth, listHeight, itemHeight, allItems });
this.setState({ listWidth, listHeight, itemHeight, allItems, typeaheadIndex: null });
}
};
......@@ -95,7 +107,8 @@ export class Typeahead extends React.PureComponent<Props, State> {
const itemCount = this.state.allItems.length;
if (itemCount) {
// Select next suggestion
let newTypeaheadIndex = modulo(this.state.typeaheadIndex + moveAmount, itemCount);
const typeaheadIndex = this.state.typeaheadIndex || 0;
let newTypeaheadIndex = modulo(typeaheadIndex + moveAmount, itemCount);
if (this.state.allItems[newTypeaheadIndex].kind === CompletionItemKind.GroupTitle) {
newTypeaheadIndex = modulo(newTypeaheadIndex + moveAmount, itemCount);
......@@ -110,7 +123,7 @@ export class Typeahead extends React.PureComponent<Props, State> {
};
insertSuggestion = () => {
if (this.props.onSelectSuggestion) {
if (this.props.onSelectSuggestion && this.state.typeaheadIndex !== null) {
this.props.onSelectSuggestion(this.state.allItems[this.state.typeaheadIndex]);
}
};
......@@ -144,6 +157,7 @@ export class Typeahead extends React.PureComponent<Props, State> {
const { allItems, listWidth, listHeight, itemHeight, hoveredItem, typeaheadIndex } = this.state;
const showDocumentation = hoveredItem || typeaheadIndex;
const documentationItem = allItems[hoveredItem ? hoveredItem : typeaheadIndex || 0];
return (
<Portal origin={origin} isOpen={isOpen} style={this.menuPosition}>
......@@ -169,7 +183,7 @@ export class Typeahead extends React.PureComponent<Props, State> {
return (
<TypeaheadItem
onClickItem={() => (this.props.onSelectSuggestion ? this.props.onSelectSuggestion(item) : {})}
isSelected={allItems[typeaheadIndex] === item}
isSelected={typeaheadIndex === null ? false : allItems[typeaheadIndex] === item}
item={item}
prefix={prefix}
style={style}
......@@ -181,9 +195,7 @@ export class Typeahead extends React.PureComponent<Props, State> {
</FixedSizeList>
</ul>
{showDocumentation && (
<TypeaheadInfo height={listHeight} item={allItems[hoveredItem ? hoveredItem : typeaheadIndex]} />
)}
{showDocumentation && <TypeaheadInfo height={listHeight} item={documentationItem} />}
</Portal>
);
}
......
......@@ -35,7 +35,6 @@
border: $panel-border;
max-height: calc(66vh);
overflow-y: scroll;
max-width: calc(66%);
overflow-x: hidden;
outline: none;
list-style: none;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment