import typeof {
SyntheticEvent,
SyntheticKeyboardEvent,
} from 'react-dom-bindings/src/events/SyntheticEvent';
import * as React from 'react';
import {useEffect, useRef, useState} from 'react';
import Button from './Button';
import ButtonIcon from './ButtonIcon';
import Icon from './Icon';
import AutoSizeInput from './Components/NativeStyleEditor/AutoSizeInput';
import styles from './SearchInput.css';
type Props = {
goToNextResult: () => void,
goToPreviousResult: () => void,
goToResult: (index: number) => void,
placeholder: string,
search: (text: string) => void,
searchIndex: number,
searchResultsCount: number,
searchText: string,
testName?: ?string,
};
export default function SearchInput({
goToNextResult,
goToPreviousResult,
goToResult,
placeholder,
search,
searchIndex,
searchResultsCount,
searchText,
testName,
}: Props): React.Node {
const inputRef = useRef<HTMLInputElement | null>(null);
const [indexDraft, setIndexDraft] = useState<string | null>(null);
const currentResultNumber = Math.min(searchIndex + 1, searchResultsCount);
const indexValue =
indexDraft !== null ? indexDraft : String(currentResultNumber);
const handleIndexChange = (event: SyntheticEvent) => {
const raw = event.currentTarget.value.replace(/[^0-9]/g, '');
if (raw === '' || searchResultsCount === 0) {
setIndexDraft(raw);
return;
}
const clamped = Math.max(
1,
Math.min(parseInt(raw, 10), searchResultsCount),
);
setIndexDraft(String(clamped));
goToResult(clamped - 1);
};
const handleIndexBlur = () => setIndexDraft(null);
const handleIndexKeyDown = (event: SyntheticKeyboardEvent) => {
if (event.key === 'Enter' || event.key === 'Escape') {
event.preventDefault();
event.currentTarget.blur();
}
};
const resetSearch = () => search('');
const handleChange = ({currentTarget}) => {
search(currentTarget.value);
};
const handleKeyPress = ({key, shiftKey}) => {
if (key === 'Enter') {
if (shiftKey) {
goToPreviousResult();
} else {
goToNextResult();
}
}
};
useEffect(() => {
if (inputRef.current === null) {
return () => {};
}
const handleKeyDown = (event: KeyboardEvent) => {
const {key, metaKey} = event;
if (key === 'f' && metaKey) {
const inputElement = inputRef.current;
if (inputElement !== null) {
inputElement.focus();
event.preventDefault();
event.stopPropagation();
}
}
};
const ownerDocumentElement = inputRef.current.ownerDocument.documentElement;
if (ownerDocumentElement === null) {
return;
}
ownerDocumentElement.addEventListener('keydown', handleKeyDown);
return () =>
ownerDocumentElement.removeEventListener('keydown', handleKeyDown);
}, []);
return (
<div className={styles.SearchInput} data-testname={testName}>
<Icon className={styles.InputIcon} type="search" />
<input
data-testname={testName ? `${testName}-Input` : undefined}
className={styles.Input}
onChange={handleChange}
onKeyPress={handleKeyPress}
placeholder={placeholder}
ref={inputRef}
value={searchText}
/>
{!!searchText && (
<React.Fragment>
<span
className={styles.IndexLabel}
data-testname={testName ? `${testName}-ResultsCount` : undefined}>
<AutoSizeInput
className={styles.IndexInput}
testName={testName ? `${testName}-ResultIndexInput` : undefined}
type="text"
inputMode="numeric"
pattern="[0-9]*"
aria-label="Go to search result number"
title="Go to search result number"
disabled={searchResultsCount === 0}
onBlur={handleIndexBlur}
onChange={handleIndexChange}
onKeyDown={handleIndexKeyDown}
value={indexValue}
/>
{' | '}
{searchResultsCount}
</span>
<div className={styles.LeftVRule} />
<Button
data-testname={testName ? `${testName}-PreviousButton` : undefined}
disabled={!searchText}
onClick={goToPreviousResult}
title={
<React.Fragment>
Scroll to previous search result (<kbd>Shift</kbd> +{' '}
<kbd>Enter</kbd>)
</React.Fragment>
}>
<ButtonIcon type="up" />
</Button>
<Button
data-testname={testName ? `${testName}-NextButton` : undefined}
disabled={!searchText}
onClick={goToNextResult}
title={
<React.Fragment>
Scroll to next search result (<kbd>Enter</kbd>)
</React.Fragment>
}>
<ButtonIcon type="down" />
</Button>
<Button
data-testname={testName ? `${testName}-ResetButton` : undefined}
disabled={!searchText}
onClick={resetSearch}
title="Reset search">
<ButtonIcon type="close" />
</Button>
</React.Fragment>
)}
</div>
);
}