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 dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
const abortControllerRef = useRef<AbortController | null>(null);
|
const abortControllerRef = useRef<AbortController | null>(null);
|
||||||
const searchCacheRef = useRef<Map<string, FileSearchResult[]>>(new Map());
|
const searchCacheRef = useRef<Map<string, FileSearchResult[]>>(new Map());
|
||||||
|
const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||||
|
|
||||||
// Search for files when query changes
|
// Search for files when query changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -309,6 +310,16 @@ export function MultiFileSearchTextarea({
|
|||||||
}
|
}
|
||||||
}, [searchResults.length, showDropdown]);
|
}, [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();
|
const dropdownPosition = getDropdownPosition();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -352,6 +363,10 @@ export function MultiFileSearchTextarea({
|
|||||||
{searchResults.map((file, index) => (
|
{searchResults.map((file, index) => (
|
||||||
<div
|
<div
|
||||||
key={file.path}
|
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 ${
|
className={`px-3 py-2 cursor-pointer text-sm ${
|
||||||
index === selectedIndex
|
index === selectedIndex
|
||||||
? 'bg-blue-50 text-blue-900'
|
? '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 { createPortal } from 'react-dom';
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
import {
|
import {
|
||||||
@@ -67,6 +67,8 @@ function getMenuPosition(anchorEl: HTMLElement) {
|
|||||||
export function FileTagTypeaheadPlugin({ projectId }: { projectId?: string }) {
|
export function FileTagTypeaheadPlugin({ projectId }: { projectId?: string }) {
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
const [options, setOptions] = useState<FileTagOption[]>([]);
|
const [options, setOptions] = useState<FileTagOption[]>([]);
|
||||||
|
const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||||
|
const lastSelectedIndexRef = useRef<number>(-1);
|
||||||
|
|
||||||
const onQueryChange = useCallback(
|
const onQueryChange = useCallback(
|
||||||
(query: string | null) => {
|
(query: string | null) => {
|
||||||
@@ -134,6 +136,20 @@ export function FileTagTypeaheadPlugin({ projectId }: { projectId?: string }) {
|
|||||||
anchorRef.current
|
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 tagResults = options.filter((r) => r.item.type === 'tag');
|
||||||
const fileResults = options.filter((r) => r.item.type === 'file');
|
const fileResults = options.filter((r) => r.item.type === 'file');
|
||||||
|
|
||||||
@@ -167,6 +183,10 @@ export function FileTagTypeaheadPlugin({ projectId }: { projectId?: string }) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={option.key}
|
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 ${
|
className={`px-3 py-2 cursor-pointer text-sm ${
|
||||||
index === selectedIndex
|
index === selectedIndex
|
||||||
? 'bg-muted text-foreground'
|
? 'bg-muted text-foreground'
|
||||||
@@ -204,6 +224,10 @@ export function FileTagTypeaheadPlugin({ projectId }: { projectId?: string }) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={option.key}
|
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 ${
|
className={`px-3 py-2 cursor-pointer text-sm ${
|
||||||
index === selectedIndex
|
index === selectedIndex
|
||||||
? 'bg-muted text-foreground'
|
? 'bg-muted text-foreground'
|
||||||
|
|||||||
Reference in New Issue
Block a user