1- import React , { ChangeEvent , ReactNode , useCallback , useMemo , useRef , useState , useEffect } from 'react' ;
1+ import React , { ChangeEvent , ReactNode , useCallback , useMemo , useRef , useState , useEffect , forwardRef , useImperativeHandle } from 'react' ;
22import { debounce } from 'lodash' ;
33import { useDispatch , useSelector } from 'react-redux' ;
44import { Hotkey , Icon , TextInput } from '@gravity-ui/uikit' ;
@@ -12,30 +12,71 @@ import {usePage} from '@/static/new-ui/hooks/usePage';
1212import { useHotkey } from '@/static/new-ui/hooks/useHotkey' ;
1313import { search } from '@/static/modules/search' ;
1414
15- export const NameFilter = ( ) : ReactNode => {
15+ export interface NameFilterHandle {
16+ focus : ( ) => void ;
17+ }
18+
19+ export interface NameFilterProps {
20+ /** Called when user navigates down out of the input (Enter or ArrowDown at end) */
21+ onNavigateDown ?: ( ) => void ;
22+ }
23+
24+ export const NameFilter = forwardRef < NameFilterHandle , NameFilterProps > ( function NameFilter ( props , ref ) : ReactNode {
1625 const dispatch = useDispatch ( ) ;
1726 const page = usePage ( ) ;
1827 const nameFilter = useSelector ( ( state ) => state . app [ page ] . nameFilter ) ;
1928 const useRegexFilter = useSelector ( ( state ) => state . app [ page ] . useRegexFilter ) ;
2029 const useMatchCaseFilter = useSelector ( ( state ) => state . app [ page ] . useMatchCaseFilter ) ;
2130 const [ testNameFilter , setNameFilter ] = useState ( nameFilter ) ;
2231 const [ isFocused , setIsFocused ] = useState ( false ) ;
32+ const [ isAllSelected , setIsAllSelected ] = useState ( false ) ;
2333 const inputRef = useRef < HTMLInputElement > ( null ) ;
2434
25- const focusSearch = useCallback ( ( ) => inputRef . current ?. focus ( ) , [ ] ) ;
35+ const focusSearch = useCallback ( ( ) => {
36+ inputRef . current ?. focus ( ) ;
37+ const length = inputRef . current ?. value . length ?? 0 ;
38+ inputRef . current ?. setSelectionRange ( length , length ) ;
39+ } , [ ] ) ;
40+
41+ useImperativeHandle ( ref , ( ) => ( {
42+ focus : focusSearch
43+ } ) , [ focusSearch ] ) ;
44+
2645 useHotkey ( 'mod+k' , focusSearch , { allowInInput : true } ) ;
2746
2847 const onKeyDown = useCallback ( ( event : React . KeyboardEvent < HTMLInputElement > ) : void => {
48+ const input = inputRef . current ;
49+
2950 if ( event . key === 'Escape' ) {
3051 event . preventDefault ( ) ;
31- if ( testNameFilter ) {
32- setNameFilter ( '' ) ;
33- search ( '' , useMatchCaseFilter , useRegexFilter , page , false , dispatch ) ;
52+ if ( isAllSelected || ! testNameFilter ) {
53+ input ?. blur ( ) ;
54+ setIsAllSelected ( false ) ;
3455 } else {
35- inputRef . current ?. blur ( ) ;
56+ input ?. select ( ) ;
57+ setIsAllSelected ( true ) ;
58+ }
59+ return ;
60+ }
61+
62+ setIsAllSelected ( false ) ;
63+
64+ if ( event . key === 'Enter' ) {
65+ event . preventDefault ( ) ;
66+ input ?. blur ( ) ;
67+ props . onNavigateDown ?.( ) ;
68+ return ;
69+ }
70+
71+ if ( event . key === 'ArrowDown' ) {
72+ const cursorAtEnd = input && input . selectionStart === input . value . length && input . selectionEnd === input . value . length ;
73+ if ( cursorAtEnd ) {
74+ event . preventDefault ( ) ;
75+ input ?. blur ( ) ;
76+ props . onNavigateDown ?.( ) ;
3677 }
3778 }
38- } , [ testNameFilter , useMatchCaseFilter , useRegexFilter , page , dispatch ] ) ;
79+ } , [ testNameFilter , isAllSelected , props . onNavigateDown ] ) ;
3980
4081 const updateNameFilter = useCallback ( debounce (
4182 ( text ) => {
@@ -153,4 +194,4 @@ export const NameFilter = (): ReactNode => {
153194 </ div >
154195 </ div >
155196 ) ;
156- } ;
197+ } ) ;
0 commit comments