mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DS Picker: first item is always active when filtering (#70071)
* Select always the first item by default when filtering * Avoid re-render when updating the selected item state
This commit is contained in:
parent
323d3c94d3
commit
6be0ca396f
@ -54,7 +54,6 @@ export function DataSourceList(props: DataSourceListProps) {
|
||||
const styles = getStyles(theme, selectedItemCssSelector);
|
||||
|
||||
const { className, current, onChange, enableKeyboardNavigation, onClickEmptyStateCTA } = props;
|
||||
// QUESTION: Should we use data from the Redux store as admin DS view does?
|
||||
const dataSources = useDatasources({
|
||||
alerting: props.alerting,
|
||||
annotations: props.annotations,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@ -68,7 +68,7 @@ export interface KeybaordNavigatableListProps {
|
||||
*/
|
||||
export function useKeyboardNavigatableList(props: KeybaordNavigatableListProps): [Record<string, string>, string] {
|
||||
const { keyboardEvents, containerRef } = props;
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||
const selectedIndex = useRef<number>(0);
|
||||
|
||||
const attributeName = 'data-role';
|
||||
const roleName = 'keyboardSelectableItem';
|
||||
@ -78,27 +78,29 @@ export function useKeyboardNavigatableList(props: KeybaordNavigatableListProps):
|
||||
const selectedAttributeName = 'data-selectedItem';
|
||||
const selectedItemCssSelector = `[${selectedAttributeName}="true"]`;
|
||||
|
||||
useEffect(() => {
|
||||
const listItems = containerRef?.current?.querySelectorAll<HTMLElement | HTMLButtonElement | HTMLAnchorElement>(
|
||||
querySelectorNavigatableElements
|
||||
);
|
||||
const selectItem = useCallback(
|
||||
(index: number) => {
|
||||
const listItems = containerRef?.current?.querySelectorAll<HTMLElement | HTMLButtonElement | HTMLAnchorElement>(
|
||||
querySelectorNavigatableElements
|
||||
);
|
||||
const selectedItem = listItems?.item(index % listItems?.length);
|
||||
|
||||
const selectedItem = listItems?.item(selectedIndex % listItems?.length);
|
||||
listItems?.forEach((li) => li.setAttribute(selectedAttributeName, 'false'));
|
||||
|
||||
listItems?.forEach((li) => li.setAttribute(selectedAttributeName, 'false'));
|
||||
if (selectedItem) {
|
||||
selectedItem.scrollIntoView({ block: 'center' });
|
||||
selectedItem.setAttribute(selectedAttributeName, 'true');
|
||||
}
|
||||
},
|
||||
[containerRef, querySelectorNavigatableElements]
|
||||
);
|
||||
|
||||
if (selectedItem) {
|
||||
selectedItem.scrollIntoView({ block: 'center' });
|
||||
selectedItem.setAttribute(selectedAttributeName, 'true');
|
||||
}
|
||||
}, [selectedIndex, containerRef, selectedAttributeName, querySelectorNavigatableElements]);
|
||||
|
||||
const clickSelectedElement = () => {
|
||||
const clickSelectedElement = useCallback(() => {
|
||||
containerRef?.current
|
||||
?.querySelector<HTMLElement | HTMLButtonElement | HTMLAnchorElement>(selectedItemCssSelector)
|
||||
?.querySelector<HTMLButtonElement>('button') // This is a bit weird. The main use for this would be to select card items, however the root of the card component does not have the click event handler, instead it's attached to a button inside it.
|
||||
?.click();
|
||||
};
|
||||
}, [containerRef, selectedItemCssSelector]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!keyboardEvents) {
|
||||
@ -108,12 +110,13 @@ export function useKeyboardNavigatableList(props: KeybaordNavigatableListProps):
|
||||
next: (keyEvent) => {
|
||||
switch (keyEvent?.code) {
|
||||
case 'ArrowDown': {
|
||||
setSelectedIndex(selectedIndex + 1);
|
||||
selectItem(++selectedIndex.current);
|
||||
keyEvent.preventDefault();
|
||||
break;
|
||||
}
|
||||
case 'ArrowUp':
|
||||
setSelectedIndex(selectedIndex > 0 ? selectedIndex - 1 : selectedIndex);
|
||||
selectedIndex.current = selectedIndex.current > 0 ? selectedIndex.current - 1 : selectedIndex.current;
|
||||
selectItem(selectedIndex.current);
|
||||
keyEvent.preventDefault();
|
||||
break;
|
||||
case 'Enter':
|
||||
@ -123,7 +126,31 @@ export function useKeyboardNavigatableList(props: KeybaordNavigatableListProps):
|
||||
},
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
}, [keyboardEvents, selectItem, clickSelectedElement]);
|
||||
|
||||
useEffect(() => {
|
||||
// This observer is used to keep track of the number of items in the list
|
||||
// that can change dinamically (e.g. when filtering a dropdown list)
|
||||
const listObserver = new MutationObserver((mutations) => {
|
||||
const listHasChanged = mutations.some(
|
||||
(mutation) =>
|
||||
(mutation.addedNodes && mutation.addedNodes.length > 0) ||
|
||||
(mutation.removedNodes && mutation.removedNodes.length > 0)
|
||||
);
|
||||
|
||||
listHasChanged && selectItem(0);
|
||||
});
|
||||
|
||||
if (containerRef.current) {
|
||||
listObserver.observe(containerRef.current, {
|
||||
childList: true,
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
listObserver.disconnect();
|
||||
};
|
||||
}, [containerRef, querySelectorNavigatableElements, selectItem]);
|
||||
|
||||
return [navigatableItemProps, selectedItemCssSelector];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user