UNPKG

at-react-autocomplete-1

Version:

An auto complete dropdown component for React with TypeScript support.

202 lines (200 loc) 7.34 kB
"use strict"; "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;