import * as React from 'react'; import { useHtmlElementEventListener } from '../useHtmlElementEventListener'; import { useHotkey } from '../hotkeys/useHotkey'; import { useTree } from '../tree/Tree'; import { useTreeEnvironment } from '../controlledEnvironment/ControlledTreeEnvironment'; import { useSearchMatchFocus } from './useSearchMatchFocus'; import { useViewState } from '../tree/useViewState'; import { useCallSoon } from '../useCallSoon'; import { getDocument } from '../utils'; export const SearchInput: React.FC<{ containerRef?: HTMLElement; }> = ({ containerRef }) => { const { search, setSearch, treeId, renderers, renamingItem } = useTree(); const environment = useTreeEnvironment(); useViewState(); const isActiveTree = environment.activeTreeId === treeId; const callSoon = useCallSoon(); useSearchMatchFocus(); const clearSearch = () => { setSearch(null); if (environment.autoFocus ?? true) { // Refocus item in tree // TODO move logic as reusable method into tree or tree environment const focusItem = getDocument()?.querySelector( `[data-rct-tree="${treeId}"] [data-rct-item-focus="true"]` ); (focusItem as HTMLElement)?.focus?.(); } }; useHotkey( 'abortSearch', () => { // Without the callSoon, hitting enter to abort // and then moving focus weirdly moves the selected item along // with the focused item. callSoon(() => { clearSearch(); }); }, isActiveTree && search !== null, true ); useHtmlElementEventListener(containerRef, 'keydown', e => { const unicode = e.key.charCodeAt(0); if ( (environment.canSearch ?? true) && (environment.canSearchByStartingTyping ?? true) && isActiveTree && search === null && !renamingItem && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey && ((unicode >= 48 && unicode <= 57) || // number // (unicode >= 65 && unicode <= 90) || // uppercase letter (unicode >= 97 && unicode <= 122)) // lowercase letter ) { setSearch(''); } }); if (!(environment.canSearch ?? true) || search === null) { return null; } return renderers.renderSearchInput({ inputProps: { value: search, onChange: (e: any) => setSearch(e.target.value), onBlur: () => { clearSearch(); }, ref: el => { el?.focus?.(); }, 'aria-label': 'Search for items', // TODO ...({ 'data-rct-search-input': 'true', } as any), }, }) as any; };