diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json
index 47a71b295..70662d9f2 100644
--- a/src/frontend/package-lock.json
+++ b/src/frontend/package-lock.json
@@ -709,6 +709,7 @@
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
+ "extraneous": true,
"inBundle": true,
"license": "MIT",
"engines": {
diff --git a/src/frontend/src/CustomNodes/GenericNode/components/ListSelectionComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/ListSelectionComponent/index.tsx
index 0d8bd5ea3..989a058aa 100644
--- a/src/frontend/src/CustomNodes/GenericNode/components/ListSelectionComponent/index.tsx
+++ b/src/frontend/src/CustomNodes/GenericNode/components/ListSelectionComponent/index.tsx
@@ -1,15 +1,16 @@
import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import ShadTooltip from "@/components/common/shadTooltipComponent";
import SearchBarComponent from "@/components/core/parameterRenderComponent/components/searchBarComponent";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent } from "@/components/ui/dialog-with-no-close";
import { cn } from "@/utils/utils";
-import { useCallback, useMemo, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
// Update interface with better types
interface ListSelectionComponentProps {
open: boolean;
- options: any[];
onClose: () => void;
+ options: any[];
setSelectedList: (action: any[]) => void;
selectedList: any[];
searchCategories?: string[];
@@ -22,41 +23,99 @@ const ListItem = ({
isSelected,
onClick,
className,
+ onMouseEnter,
+ onMouseLeave,
+ isFocused,
+ isKeyboardNavActive,
}: {
item: any;
isSelected: boolean;
onClick: () => void;
className?: string;
-}) => (
-
-);
+ onClick={onClick}
+ onMouseEnter={() => {
+ if (!isKeyboardNavActive) {
+ setIsHovered(true);
+ onMouseEnter();
+ }
+ }}
+ onMouseLeave={() => {
+ setIsHovered(false);
+ onMouseLeave();
+ }}
+ // Disable pointer events during keyboard navigation
+ style={{ pointerEvents: isKeyboardNavActive ? "none" : "auto" }}
+ >
+
+ {item.icon && (
+
+ )}
+
{item.name}
+ {"metaData" in item && item.metaData && (
+
{item.metaData}
+ )}
+ {isHovered || isFocused ? (
+
+ ) : (
+ // Always show the check icon when selected, regardless of hover/focus state
+ isSelected && (
+
+ )
+ )}
+
+
+ );
+};
const ListSelectionComponent = ({
open,
@@ -69,6 +128,10 @@ const ListSelectionComponent = ({
limit = 1,
}: ListSelectionComponentProps) => {
const [search, setSearch] = useState("");
+ const [hoveredItem, setHoveredItem] = useState(null);
+ const [focusedIndex, setFocusedIndex] = useState(-1);
+ const [isKeyboardNavActive, setIsKeyboardNavActive] = useState(false);
+ const listContainerRef = useRef(null);
const filteredList = useMemo(() => {
if (!search.trim()) {
@@ -110,19 +173,96 @@ const ListSelectionComponent = ({
},
]);
onClose();
- setSearch("");
}
},
- [selectedList, setSelectedList, onClose, limit],
+ [selectedList, setSelectedList, limit, onClose],
);
- const handleCloseDialog = useCallback(() => {
- onClose();
- }, [onClose]);
+ // Reset focus state when filtered list changes
+ useEffect(() => {
+ if (open) {
+ if (filteredList.length > 0) {
+ setFocusedIndex(0);
+ setHoveredItem(filteredList[0]);
+ } else {
+ setFocusedIndex(-1);
+ setHoveredItem(null);
+ }
+ }
+ }, [open, filteredList.length]);
+
+ // Reset search when dialog opens
+ useEffect(() => {
+ if (open) {
+ setSearch("");
+ setIsKeyboardNavActive(false);
+ }
+ }, [open]);
+
+ // Handle keyboard navigation
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (filteredList.length === 0) return;
+
+ switch (e.key) {
+ case "ArrowDown":
+ e.preventDefault();
+ setIsKeyboardNavActive(true);
+ setFocusedIndex((prev) => {
+ const newIndex = prev < filteredList.length - 1 ? prev + 1 : 0;
+ setHoveredItem(filteredList[newIndex]);
+ return newIndex;
+ });
+ break;
+ case "ArrowUp":
+ e.preventDefault();
+ setIsKeyboardNavActive(true);
+ setFocusedIndex((prev) => {
+ const newIndex = prev > 0 ? prev - 1 : filteredList.length - 1;
+ setHoveredItem(filteredList[newIndex]);
+ return newIndex;
+ });
+ break;
+ case "Enter":
+ if (hoveredItem) {
+ e.preventDefault();
+ handleSelectAction(hoveredItem);
+ if (onSelection) {
+ onSelection(hoveredItem);
+ }
+ }
+ break;
+ case "Escape":
+ e.preventDefault();
+ onClose();
+ break;
+ }
+ },
+ [filteredList, hoveredItem, handleSelectAction, onSelection, onClose],
+ );
+
+ // Detect mouse movement to switch from keyboard to mouse navigation
+ useEffect(() => {
+ const handleMouseMove = () => {
+ if (isKeyboardNavActive) {
+ setIsKeyboardNavActive(false);
+ }
+ };
+
+ if (open) {
+ window.addEventListener("mousemove", handleMouseMove);
+ return () => {
+ window.removeEventListener("mousemove", handleMouseMove);
+ };
+ }
+ }, [open, isKeyboardNavActive]);
return (
-