move viewport with arrow key navigation in file-tag-typeahead-plugin and multi-file-searcharea (#1406)
This commit is contained in:
committed by
GitHub
parent
72f2ab1320
commit
18637ab3e1
@@ -44,6 +44,7 @@ export function MultiFileSearchTextarea({
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
const searchCacheRef = useRef<Map<string, FileSearchResult[]>>(new Map());
|
||||
const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||
|
||||
// Search for files when query changes
|
||||
useEffect(() => {
|
||||
@@ -309,6 +310,16 @@ export function MultiFileSearchTextarea({
|
||||
}
|
||||
}, [searchResults.length, showDropdown]);
|
||||
|
||||
// Scroll selected item into view when navigating with arrow keys
|
||||
useEffect(() => {
|
||||
if (selectedIndex >= 0) {
|
||||
const itemEl = itemRefs.current.get(selectedIndex);
|
||||
if (itemEl) {
|
||||
itemEl.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}
|
||||
}, [selectedIndex]);
|
||||
|
||||
const dropdownPosition = getDropdownPosition();
|
||||
|
||||
return (
|
||||
@@ -352,6 +363,10 @@ export function MultiFileSearchTextarea({
|
||||
{searchResults.map((file, index) => (
|
||||
<div
|
||||
key={file.path}
|
||||
ref={(el) => {
|
||||
if (el) itemRefs.current.set(index, el);
|
||||
else itemRefs.current.delete(index);
|
||||
}}
|
||||
className={`px-3 py-2 cursor-pointer text-sm ${
|
||||
index === selectedIndex
|
||||
? 'bg-blue-50 text-blue-900'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
import {
|
||||
@@ -67,6 +67,8 @@ function getMenuPosition(anchorEl: HTMLElement) {
|
||||
export function FileTagTypeaheadPlugin({ projectId }: { projectId?: string }) {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const [options, setOptions] = useState<FileTagOption[]>([]);
|
||||
const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||
const lastSelectedIndexRef = useRef<number>(-1);
|
||||
|
||||
const onQueryChange = useCallback(
|
||||
(query: string | null) => {
|
||||
@@ -134,6 +136,20 @@ export function FileTagTypeaheadPlugin({ projectId }: { projectId?: string }) {
|
||||
anchorRef.current
|
||||
);
|
||||
|
||||
// Scroll selected item into view when navigating with arrow keys
|
||||
if (
|
||||
selectedIndex !== null &&
|
||||
selectedIndex !== lastSelectedIndexRef.current
|
||||
) {
|
||||
lastSelectedIndexRef.current = selectedIndex;
|
||||
setTimeout(() => {
|
||||
const itemEl = itemRefs.current.get(selectedIndex);
|
||||
if (itemEl) {
|
||||
itemEl.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
const tagResults = options.filter((r) => r.item.type === 'tag');
|
||||
const fileResults = options.filter((r) => r.item.type === 'file');
|
||||
|
||||
@@ -167,6 +183,10 @@ export function FileTagTypeaheadPlugin({ projectId }: { projectId?: string }) {
|
||||
return (
|
||||
<div
|
||||
key={option.key}
|
||||
ref={(el) => {
|
||||
if (el) itemRefs.current.set(index, el);
|
||||
else itemRefs.current.delete(index);
|
||||
}}
|
||||
className={`px-3 py-2 cursor-pointer text-sm ${
|
||||
index === selectedIndex
|
||||
? 'bg-muted text-foreground'
|
||||
@@ -204,6 +224,10 @@ export function FileTagTypeaheadPlugin({ projectId }: { projectId?: string }) {
|
||||
return (
|
||||
<div
|
||||
key={option.key}
|
||||
ref={(el) => {
|
||||
if (el) itemRefs.current.set(index, el);
|
||||
else itemRefs.current.delete(index);
|
||||
}}
|
||||
className={`px-3 py-2 cursor-pointer text-sm ${
|
||||
index === selectedIndex
|
||||
? 'bg-muted text-foreground'
|
||||
|
||||
Reference in New Issue
Block a user