[v10.0.x] DS Picker: first item is not active when filtering (#70256)
DS Picker: first item is always active when filtering (#70071)
(cherry picked from commit 6be0ca396f)
Co-authored-by: Ivan Ortega Alba <ivanortegaalba@gmail.com>
This commit is contained in:
co-authored by
Ivan Ortega Alba
parent
f145c251d1
commit
edd4faf064
@@ -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];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user