diff --git a/frontend/src/components/floating-menus/NodeCatalog.svelte b/frontend/src/components/floating-menus/NodeCatalog.svelte index 58856e2ac8..c9f3935624 100644 --- a/frontend/src/components/floating-menus/NodeCatalog.svelte +++ b/frontend/src/components/floating-menus/NodeCatalog.svelte @@ -19,6 +19,9 @@ $: nodeCategories = buildNodeCategories($nodeGraph.nodeTypes, searchTerm); + let focusedNodeIndex = 0; + $: flatNodeList = nodeCategories.flatMap((node) => node[1].nodes); + type NodeCategoryDetails = { nodes: FrontendNodeType[]; open: boolean; @@ -29,6 +32,7 @@ const isTypeSearch = searchTerm.toLowerCase().startsWith("type:"); let typeSearchTerm = ""; let remainingSearchTerms = [searchTerm.toLowerCase()]; + focusedNodeIndex = 0; if (isTypeSearch) { // Extract the first word after "type:" as the type search @@ -104,11 +108,32 @@ }); } + function keyboardNavigationHandler(e: KeyboardEvent) { + const listLength: number = flatNodeList.length; + if (listLength === 0) return; + + if (e.key === "ArrowDown") { + focusedNodeIndex = (focusedNodeIndex + 1) % listLength; + e.preventDefault(); + } else if (e.key === "ArrowUp") { + focusedNodeIndex = (focusedNodeIndex - 1 + listLength) % listLength; + e.preventDefault(); + } else if (e.key === "Enter") { + const node = flatNodeList[focusedNodeIndex]; + dispatch("selectNodeType", node.name); + } else { + return; + } + + setTimeout(() => document.querySelector(".node-catalog button.focused")?.scrollIntoView({ block: "nearest" }), 0); + } + onMount(() => { setTimeout(() => nodeSearchInput?.focus(), 0); }); +
(searchTerm = detail)} bind:this={nodeSearchInput} />
@@ -118,7 +143,13 @@ {nodeCategory[0]} {#each nodeCategory[1].nodes as nodeType} - dispatch("selectNodeType", nodeType.name)} /> + dispatch("selectNodeType", nodeType.name)} + /> {/each} {:else} diff --git a/frontend/src/components/widgets/buttons/TextButton.svelte b/frontend/src/components/widgets/buttons/TextButton.svelte index 8cfc3ec4d5..893edf2b8f 100644 --- a/frontend/src/components/widgets/buttons/TextButton.svelte +++ b/frontend/src/components/widgets/buttons/TextButton.svelte @@ -15,6 +15,7 @@ export let icon: IconName | undefined = undefined; export let hoverIcon: IconName | undefined = undefined; export let emphasized = false; + export let focused = false; export let flush = false; export let minWidth = 0; export let disabled = false; @@ -54,6 +55,7 @@ class:open={self?.open} class:hover-icon={hoverIcon && !disabled} class:emphasized + class:focused class:disabled class:narrow class:flush @@ -118,7 +120,8 @@ } &:hover, - &.open { + &.open, + &.focused { --button-background-color: var(--color-6-lowergray); --button-text-color: var(--color-f-white); } @@ -143,7 +146,8 @@ --button-text-color: var(--color-2-mildblack); &:hover, - &.open { + &.open, + &.focused { --button-background-color: var(--color-f-white); } @@ -157,7 +161,8 @@ --button-text-color: var(--color-e-nearwhite); &:hover, - &.open { + &.open, + &.focused { --button-background-color: var(--color-5-dullgray); } }