@grafana/ui
Version:
Grafana Components Library
1 lines • 15 kB
Source Map (JSON)
{"version":3,"file":"Typeahead.mjs","sources":["../../../../src/components/Typeahead/Typeahead.tsx"],"sourcesContent":["import { css } from '@emotion/css';\nimport { isEqual } from 'lodash';\nimport { createRef, PureComponent } from 'react';\nimport * as React from 'react';\nimport ReactDOM from 'react-dom';\nimport { FixedSizeList } from 'react-window';\n\nimport { GrafanaTheme2, ThemeContext } from '@grafana/data';\n\nimport { CompletionItem, CompletionItemGroup, CompletionItemKind } from '../../types/completion';\nimport { flattenGroupItems, calculateLongestLabel, calculateListSizes } from '../../utils/typeahead';\n\nimport { TypeaheadInfo } from './TypeaheadInfo';\nimport { TypeaheadItem } from './TypeaheadItem';\n\nconst modulo = (a: number, n: number) => a - n * Math.floor(a / n);\n\ninterface Props {\n origin: string;\n groupedItems: CompletionItemGroup[];\n prefix?: string;\n menuRef?: (el: Typeahead) => void;\n onSelectSuggestion?: (suggestion: CompletionItem) => void;\n isOpen?: boolean;\n}\n\nexport interface State {\n allItems: CompletionItem[];\n listWidth: number;\n listHeight: number;\n itemHeight: number;\n hoveredItem: number | null;\n typeaheadIndex: number | null;\n}\n\nexport class Typeahead extends PureComponent<Props, State> {\n static contextType = ThemeContext;\n context!: React.ContextType<typeof ThemeContext>;\n listRef = createRef<FixedSizeList>();\n\n state: State = {\n hoveredItem: null,\n typeaheadIndex: null,\n allItems: [],\n listWidth: -1,\n listHeight: -1,\n itemHeight: -1,\n };\n\n componentDidMount = () => {\n if (this.props.menuRef) {\n this.props.menuRef(this);\n }\n\n document.addEventListener('selectionchange', this.handleSelectionChange);\n\n const allItems = flattenGroupItems(this.props.groupedItems);\n const longestLabel = calculateLongestLabel(allItems);\n const { listWidth, listHeight, itemHeight } = calculateListSizes(this.context, allItems, longestLabel);\n this.setState({\n listWidth,\n listHeight,\n itemHeight,\n allItems,\n });\n };\n\n componentWillUnmount = () => {\n document.removeEventListener('selectionchange', this.handleSelectionChange);\n };\n\n handleSelectionChange = () => {\n this.forceUpdate();\n };\n\n componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>) => {\n if (\n this.state.typeaheadIndex !== null &&\n prevState.typeaheadIndex !== this.state.typeaheadIndex &&\n this.listRef &&\n this.listRef.current\n ) {\n if (this.state.typeaheadIndex === 1) {\n this.listRef.current.scrollToItem(0); // special case for handling the first group label\n return;\n }\n this.listRef.current.scrollToItem(this.state.typeaheadIndex);\n }\n\n if (isEqual(prevProps.groupedItems, this.props.groupedItems) === false) {\n const allItems = flattenGroupItems(this.props.groupedItems);\n const longestLabel = calculateLongestLabel(allItems);\n const { listWidth, listHeight, itemHeight } = calculateListSizes(this.context, allItems, longestLabel);\n this.setState({ listWidth, listHeight, itemHeight, allItems, typeaheadIndex: null });\n }\n };\n\n onMouseEnter = (index: number) => {\n this.setState({\n hoveredItem: index,\n });\n };\n\n onMouseLeave = () => {\n this.setState({\n hoveredItem: null,\n });\n };\n\n moveMenuIndex = (moveAmount: number) => {\n const itemCount = this.state.allItems.length;\n if (itemCount) {\n // Select next suggestion\n const typeaheadIndex = this.state.typeaheadIndex || 0;\n let newTypeaheadIndex = modulo(typeaheadIndex + moveAmount, itemCount);\n\n if (this.state.allItems[newTypeaheadIndex].kind === CompletionItemKind.GroupTitle) {\n newTypeaheadIndex = modulo(newTypeaheadIndex + moveAmount, itemCount);\n }\n\n this.setState({\n typeaheadIndex: newTypeaheadIndex,\n });\n\n return;\n }\n };\n\n insertSuggestion = () => {\n if (this.props.onSelectSuggestion && this.state.typeaheadIndex !== null) {\n this.props.onSelectSuggestion(this.state.allItems[this.state.typeaheadIndex]);\n }\n };\n\n get menuPosition(): string {\n // Exit for unit tests\n if (!window.getSelection) {\n return '';\n }\n\n const selection = window.getSelection();\n const node = selection && selection.anchorNode;\n\n // Align menu overlay to editor node\n if (node && node.parentElement) {\n // Read from DOM\n const rect = node.parentElement.getBoundingClientRect();\n const scrollX = window.scrollX;\n const scrollY = window.scrollY;\n\n return `position: absolute; display: flex; top: ${rect.top + scrollY + rect.height + 6}px; left: ${\n rect.left + scrollX - 2\n }px`;\n }\n\n return '';\n }\n\n render() {\n const { prefix, isOpen = false, origin } = this.props;\n const { allItems, listWidth, listHeight, itemHeight, hoveredItem, typeaheadIndex } = this.state;\n const styles = getStyles(this.context);\n\n const showDocumentation = hoveredItem || typeaheadIndex;\n const documentationItem = allItems[hoveredItem ? hoveredItem : typeaheadIndex || 0];\n\n return (\n <Portal origin={origin} isOpen={isOpen} style={this.menuPosition}>\n <ul role=\"menu\" className={styles.typeahead} data-testid=\"typeahead\">\n <FixedSizeList\n ref={this.listRef}\n itemCount={allItems.length}\n itemSize={itemHeight}\n itemKey={(index) => {\n const item = allItems && allItems[index];\n const key = item ? `${index}-${item.label}` : `${index}`;\n return key;\n }}\n width={listWidth}\n height={listHeight}\n >\n {({ index, style }) => {\n const item = allItems && allItems[index];\n if (!item) {\n return null;\n }\n\n return (\n <TypeaheadItem\n onClickItem={() => (this.props.onSelectSuggestion ? this.props.onSelectSuggestion(item) : {})}\n isSelected={typeaheadIndex === null ? false : allItems[typeaheadIndex] === item}\n item={item}\n prefix={prefix}\n style={style}\n onMouseEnter={() => this.onMouseEnter(index)}\n onMouseLeave={this.onMouseLeave}\n />\n );\n }}\n </FixedSizeList>\n </ul>\n\n {showDocumentation && <TypeaheadInfo height={listHeight} item={documentationItem} />}\n </Portal>\n );\n }\n}\n\ninterface PortalProps {\n index?: number;\n isOpen: boolean;\n origin: string;\n style: string;\n}\n\nclass Portal extends PureComponent<React.PropsWithChildren<PortalProps>, {}> {\n node: HTMLElement;\n\n constructor(props: React.PropsWithChildren<PortalProps>) {\n super(props);\n const { index = 0, origin = 'query', style } = props;\n this.node = document.createElement('div');\n this.node.setAttribute('style', style);\n this.node.classList.add(`slate-typeahead-${origin}-${index}`);\n document.body.appendChild(this.node);\n }\n\n componentWillUnmount() {\n document.body.removeChild(this.node);\n }\n\n render() {\n if (this.props.isOpen) {\n this.node.setAttribute('style', this.props.style);\n this.node.classList.add(`slate-typeahead--open`);\n return ReactDOM.createPortal(this.props.children, this.node);\n } else {\n this.node.classList.remove(`slate-typeahead--open`);\n }\n\n return null;\n }\n}\n\nconst getStyles = (theme: GrafanaTheme2) => ({\n typeahead: css({\n position: 'relative',\n zIndex: theme.zIndex.typeahead,\n borderRadius: theme.shape.radius.default,\n border: `1px solid ${theme.components.panel.borderColor}`,\n maxHeight: '66vh',\n overflowY: 'scroll',\n overflowX: 'hidden',\n outline: 'none',\n listStyle: 'none',\n background: theme.components.panel.background,\n color: theme.colors.text.primary,\n boxShadow: theme.shadows.z2,\n\n strong: {\n color: theme.v1.palette.yellow,\n },\n }),\n});\n"],"names":[],"mappings":"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"}