The input props.
The highlighted search input element.
<HighlightedSearchInput
value={query}
onChange={setQuery}
onValidityChange={(blocked, msg) => setError(blocked ? msg : undefined)}
/>
export default function HighlightedSearchInput({
value,
onChange,
placeholder,
disabled,
ariaLabel,
className,
style,
onKeyDown,
onValidityChange,
}: HighlightedSearchInputProps) {
const inputRef = useRef<HTMLInputElement>(null);
const backdropRef = useRef<HTMLDivElement>(null);
const highlight = useMemo(() => highlightSearchQuery(value), [value]);
// Latest-callback ref so validity is reported only when it actually changes, regardless of
// whether the parent memoizes the handler.
const onValidityChangeRef = useRef(onValidityChange);
onValidityChangeRef.current = onValidityChange;
useEffect(() => {
onValidityChangeRef.current?.(highlight.state === 'error', highlight.message);
}, [highlight.state, highlight.message]);
// Keep the backdrop scrolled in lockstep with the input's horizontal scroll.
const syncScroll = (): void => {
if (inputRef.current && backdropRef.current) {
backdropRef.current.scrollLeft = inputRef.current.scrollLeft;
}
};
useEffect(syncScroll, [value]);
return (
<div className={`${styles.wrapper} ${className ?? ''}`} style={style}>
<div
ref={backdropRef}
className={styles.backdrop}
aria-hidden="true"
dangerouslySetInnerHTML={{ __html: highlight.html }}
/>
<input
ref={inputRef}
type="text"
className={styles.input}
value={value}
placeholder={placeholder}
disabled={disabled}
aria-label={ariaLabel}
spellCheck={false}
autoComplete="off"
onChange={(event: ChangeEvent<HTMLInputElement>) => onChange(event.target.value)}
onKeyDown={onKeyDown}
onScroll={syncScroll}
/>
</div>
);
}
Search text input with live syntax highlighting for advanced (boolean) queries. A plain query renders as an ordinary input; once the query becomes advanced, the typed text is made transparent and a colored backdrop (operators, parentheses by depth, quoted phrases, terms) is shown behind the caret. Invalid advanced queries (malformed or with no inclusive term) are flagged and reported via HighlightedSearchInputProps.onValidityChange.