@navinc/base-react-components
Version:
Nav's Pattern Library
159 lines • 6.6 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { forwardRef, useRef, useEffect, useState, } from 'react';
import { BaseStringInput } from '../base-string-input/base-string-input.js';
import { useDebouncedCallback } from 'use-debounce';
import { createProxyWithOverrides } from '@navinc/utils';
import { combineRefs } from '../../combine-refs.js';
import { triggerChangeEvent } from '../../trigger-change.js';
const caseInsensitiveEqual = (a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }) === 0;
const BaseSearchInputComponent = (_a, ref) => {
var { onFocus, onChange, onBlur, value, results, resultToQuery, search, isLoading, children } = _a, props = __rest(_a, ["onFocus", "onChange", "onBlur", "value", "results", "resultToQuery", "search", "isLoading", "children"]);
const [focusedResultIndex, setFocusedResultIndex] = useState(-1);
const [canBeOpen, setCanBeOpen] = useState(false);
const [query, setQuery] = useState('');
const inputRef = useRef(null);
const combinedInputRef = combineRefs(ref, inputRef);
const lastSearchRef = useRef('');
const shouldShowDropDown = !!(query.length && canBeOpen && query === lastSearchRef.current && !isLoading);
const shouldShowResults = !!(shouldShowDropDown && results.length);
const shouldShowNoResults = !!(shouldShowDropDown && !results.length);
// sync value and query
useEffect(() => {
if (!value) {
return;
}
const newQuery = resultToQuery(value);
if (!newQuery) {
return;
}
setQuery(newQuery);
}, [resultToQuery, value]);
// update the value once the results are loaded if the current query is one of the results
useEffect(() => {
if (!value && !!query) {
const result = results.find((result) => caseInsensitiveEqual(resultToQuery(result), query));
if (result) {
if (inputRef.current) {
inputRef.current.value = resultToQuery(result);
triggerChangeEvent(inputRef.current);
}
}
}
}, [query, resultToQuery, results, value]);
const searchDebounced = useDebouncedCallback((query) => {
search(query);
setFocusedResultIndex(-1);
}, 500);
const handleChange = (e) => {
var _a;
const updatedEvent = e;
const newQuery = e.target.value;
setQuery(newQuery);
const result = (_a = results.find((result) => caseInsensitiveEqual(resultToQuery(result), newQuery))) !== null && _a !== void 0 ? _a : undefined;
updatedEvent.target = createProxyWithOverrides(updatedEvent.target, { value: result });
onChange === null || onChange === void 0 ? void 0 : onChange(updatedEvent);
if (newQuery.length > 0 && !caseInsensitiveEqual(newQuery, lastSearchRef.current)) {
lastSearchRef.current = newQuery;
searchDebounced(newQuery);
setCanBeOpen(true);
}
};
const handleInputFocus = (event) => {
setCanBeOpen(true);
onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
};
const goToNextResult = () => {
const newIndex = focusedResultIndex + 1;
if (newIndex < results.length) {
setFocusedResultIndex(newIndex);
}
};
const goToPreviousResult = () => {
const newIndex = focusedResultIndex - 1;
if (newIndex > -2) {
setFocusedResultIndex(newIndex);
}
};
const closeResults = () => {
setCanBeOpen(false);
setFocusedResultIndex(-1);
};
const onSelectResult = (selectedResult) => {
closeResults();
if (inputRef.current) {
const query = resultToQuery(selectedResult);
inputRef.current.value = query;
lastSearchRef.current = query;
triggerChangeEvent(inputRef.current);
}
};
const handleBlurOnInput = (event) => {
closeResults();
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
};
const handleKeyDownOnInput = (event) => {
switch (event.key) {
case 'ArrowDown':
if (shouldShowResults) {
event.preventDefault();
goToNextResult();
}
else {
setCanBeOpen(true);
}
break;
case 'Tab':
if (shouldShowResults) {
event.preventDefault();
if (event.shiftKey) {
goToPreviousResult();
}
else {
goToNextResult();
}
}
break;
case 'ArrowUp':
if (shouldShowResults) {
event.preventDefault();
goToPreviousResult();
}
break;
case 'Escape':
if (focusedResultIndex > -1) {
setFocusedResultIndex(-1);
}
else {
setQuery('');
}
break;
case 'Enter':
if (focusedResultIndex > -1) {
event.preventDefault();
onSelectResult(results[focusedResultIndex]);
}
break;
}
};
return (_jsxs(_Fragment, { children: [_jsx(BaseStringInput, Object.assign({ ref: combinedInputRef, autoComplete: "off", onBlur: handleBlurOnInput, onChange: handleChange, onFocus: handleInputFocus, onKeyDown: handleKeyDownOnInput, value: query }, props)), children({
shouldShowResults,
shouldShowNoResults,
query,
results,
focusedResultIndex,
handleResultSelect: onSelectResult,
})] }));
};
export const BaseSearchInput = forwardRef(BaseSearchInputComponent);
//# sourceMappingURL=base-search-input.js.map