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
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user