at-react-autocomplete-1
Version:
An auto complete dropdown component for React with TypeScript support.
202 lines (200 loc) • 7.34 kB
JavaScript
;
"use client";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/Index.tsx
var Index_exports = {};
__export(Index_exports, {
default: () => Index_default
});
module.exports = __toCommonJS(Index_exports);
var import_react = require("react");
var import_jsx_runtime = require("react/jsx-runtime");
function AutocompleteDropdown({
suggestions,
onSelect,
onInputChange,
renderItem,
getDisplayValue,
placeholder = "Search...",
isLoading = false,
inputValue,
setInputValue,
minSearchLength = 2,
debounceDelay = 300,
className = "",
inputClassName = "",
onEnter
}) {
const [internalValue, setInternalValue] = (0, import_react.useState)("");
const value = inputValue !== void 0 ? inputValue : internalValue;
const updateValue = setInputValue !== void 0 ? setInputValue : setInternalValue;
const [showDropdown, setShowDropdown] = (0, import_react.useState)(false);
const [highlightedIndex, setHighlightedIndex] = (0, import_react.useState)(-1);
const dropdownRef = (0, import_react.useRef)(null);
const suggestionsListRef = (0, import_react.useRef)(null);
const inputRef = (0, import_react.useRef)(null);
const abortControllerRef = (0, import_react.useRef)(null);
const defaultGetDisplayValue = (item) => {
if (typeof item === "string") return item;
if (item && typeof item === "object" && "label" in item) {
return String(item.label);
}
return "";
};
const getDisplay = getDisplayValue != null ? getDisplayValue : defaultGetDisplayValue;
const render = renderItem != null ? renderItem : (item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: getDisplay(item) });
const debounce = (0, import_react.useCallback)(
(func) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), debounceDelay);
};
},
[debounceDelay]
);
const debouncedInputChange = (0, import_react.useCallback)(
debounce((val) => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
if (val.length >= minSearchLength) {
onInputChange(val);
} else {
onInputChange("");
}
}),
[onInputChange, minSearchLength]
);
const handleInputChange = (e) => {
const val = e.target.value;
updateValue(val);
debouncedInputChange(val);
setShowDropdown(val.length > 0);
setHighlightedIndex(-1);
};
const handleSuggestionClick = (item) => {
var _a;
onSelect(item);
setShowDropdown(false);
updateValue(getDisplay(item));
(_a = inputRef.current) == null ? void 0 : _a.focus();
};
const handleKeyDown = (e) => {
if (!showDropdown) return;
switch (e.key) {
case "ArrowDown":
e.preventDefault();
setHighlightedIndex(
(prev) => prev < suggestions.length - 1 ? prev + 1 : prev
);
scrollIntoView(highlightedIndex + 1);
break;
case "ArrowUp":
e.preventDefault();
setHighlightedIndex((prev) => prev > 0 ? prev - 1 : 0);
scrollIntoView(highlightedIndex - 1);
break;
case "Enter":
e.preventDefault();
if (highlightedIndex >= 0 && highlightedIndex < suggestions.length) {
const item = suggestions[highlightedIndex];
onSelect(item);
setShowDropdown(false);
updateValue(getDisplay(item));
} else {
setShowDropdown(false);
if (onEnter) {
onEnter(value);
}
}
break;
case "Escape":
e.preventDefault();
setShowDropdown(false);
break;
}
};
const scrollIntoView = (index) => {
if (suggestionsListRef.current && index >= 0 && index < suggestions.length) {
const itemElement = suggestionsListRef.current.children[index];
if (itemElement) {
itemElement.scrollIntoView({ block: "nearest" });
}
}
};
(0, import_react.useEffect)(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setShowDropdown(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, []);
(0, import_react.useEffect)(() => {
var _a;
if (((_a = value == null ? void 0 : value.length) != null ? _a : 0) > 0 && suggestions.length > 0) {
setShowDropdown(true);
} else {
setShowDropdown(false);
}
}, [suggestions, value]);
(0, import_react.useEffect)(() => {
setHighlightedIndex(-1);
}, [suggestions]);
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `relative w-full border ${className}`, ref: dropdownRef, children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"input",
{
type: "text",
ref: inputRef,
value,
onChange: handleInputChange,
onKeyDown: handleKeyDown,
onFocus: () => {
var _a;
if (((_a = value == null ? void 0 : value.length) != null ? _a : 0) >= minSearchLength) {
setShowDropdown(true);
}
},
className: `w-full focus:outline-none ${inputClassName}`,
placeholder
}
),
isLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute z-10 w-full p-2 border rounded-md shadow-lg", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-center p-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "animate-spin rounded-full h-5 w-5 border-b-2 mr-2 " }) }) }),
showDropdown && !isLoading && suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute z-10 w-full mt-2 border rounded-md shadow-lg max-h-60 overflow-y-auto", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { ref: suggestionsListRef, children: suggestions.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"li",
{
className: ` cursor-pointer ${highlightedIndex === index ? "bg-accent" : ""}`,
onMouseDown: () => handleSuggestionClick(item),
onMouseEnter: () => setHighlightedIndex(index),
children: render(item)
},
index
)) }) })
] });
}
var Index_default = AutocompleteDropdown;