From 34f61934a1e6d3ca601c2418557da6cb0ba25bc3 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 11 May 2020 14:01:35 +0200 Subject: [PATCH] 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 --- .../components/Typeahead/Typeahead.test.tsx | 46 +++++++++++++++++++ .../src/components/Typeahead/Typeahead.tsx | 34 +++++++++----- public/sass/components/_slate_editor.scss | 1 - 3 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx diff --git a/packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx b/packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx new file mode 100644 index 00000000000..34cac0ba896 --- /dev/null +++ b/packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx @@ -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(); + expect(component.find('.typeahead')).toHaveLength(0); + }); + it('renders nothing when items given', () => { + const component = mount(); + expect(component.find('.typeahead')).toHaveLength(0); + }); + }); + describe('when open', () => { + it('renders given items and nothing is selected', () => { + const component = mount(); + 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(); + 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(); + }); +}); diff --git a/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx b/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx index 10f5c3d8736..20526e17d9c 100644 --- a/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx +++ b/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx @@ -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 { @@ -34,7 +34,14 @@ export class Typeahead extends React.PureComponent { context!: React.ContextType; listRef = createRef(); - 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 { }; componentDidUpdate = (prevProps: Readonly, prevState: Readonly) => { - 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 { 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 { 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 { }; 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 { const { allItems, listWidth, listHeight, itemHeight, hoveredItem, typeaheadIndex } = this.state; const showDocumentation = hoveredItem || typeaheadIndex; + const documentationItem = allItems[hoveredItem ? hoveredItem : typeaheadIndex || 0]; return ( @@ -169,7 +183,7 @@ export class Typeahead extends React.PureComponent { return ( (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 { - {showDocumentation && ( - - )} + {showDocumentation && } ); } diff --git a/public/sass/components/_slate_editor.scss b/public/sass/components/_slate_editor.scss index 1870794c774..010180aa4e6 100644 --- a/public/sass/components/_slate_editor.scss +++ b/public/sass/components/_slate_editor.scss @@ -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;