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:
Ivan Ortega Alba 2023-06-16 17:30:49 +02:00 committed by GitHub
parent 323d3c94d3
commit 6be0ca396f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 20 deletions

View File

@ -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,

View File

@ -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];
}