UNPKG

solid-suggest

Version:

Headless search suggestion dropdown UI library for SolidJS

141 lines (138 loc) 4.74 kB
import { template, addEventListener, insert, memo, effect, setAttribute } from 'solid-js/web'; import { createSignal, createMemo, createEffect } from 'solid-js'; var _tmpl$ = /*#__PURE__*/template(`<div class=s-sug-container role=combobox aria-haspopup=listbox><input type=search class=s-sug-search aria-autocomplete=list>`), _tmpl$2 = /*#__PURE__*/template(`<ul class=s-sug-suggestions role=listbox>`), _tmpl$3 = /*#__PURE__*/template(`<li class=s-sug-suggestion role=option>`); function Suggest(props) { // Suggestions to be rendered const [suggestions, setSuggestions] = createSignal([]); const numSuggestions = createMemo(() => suggestions().length); // Basic signals for component state const [query, setQuery] = createSignal(''); const [staged, setStaged] = createSignal(null); // Internal signal for user input, separate from query to handle optional debounce const [debouncedQuery, setDebouncedQuery] = createSignal(''); // Just a variable to store debounce timeout, if applicable let debounceTimeout = null; // Effect to update suggestions when debouncedQuery changes createEffect(() => { const result = props.onQuery(debouncedQuery()); if (result instanceof Promise) { result.then(setSuggestions); } else { setSuggestions(result); } }); function handleInput(e) { const value = e.currentTarget.value; setQuery(value); if (typeof props.debounceMs === 'number' && props.debounceMs > 0) { if (debounceTimeout) clearTimeout(debounceTimeout); debounceTimeout = setTimeout(() => { setDebouncedQuery(value); }, props.debounceMs); } else { setDebouncedQuery(value); } } function stageSuggestion(index) { setStaged(index); } function stageNextSuggestion() { setStaged(s => { if (suggestions().length === 0) return null; const current = s ?? -1; return (current + 1) % numSuggestions(); }); } function stagePrevSuggestion() { setStaged(s => { if (numSuggestions() === 0) return null; const current = s ?? 0; return (current - 1 + numSuggestions()) % numSuggestions(); }); } function selectStagedSuggestion() { const index = staged(); if (index !== null) { props.onSelect(suggestions()[index]); reset(); } } function reset() { setQuery(''); setDebouncedQuery(''); setStaged(null); if (debounceTimeout) clearTimeout(debounceTimeout); } // Handles special input for suggestion behavior function handleKeyDown(e) { const keyInputReversed = props.reverseKeyInput ?? false; if (e.key === 'ArrowDown') { e.preventDefault(); if (keyInputReversed) { stagePrevSuggestion(); } else { stageNextSuggestion(); } } else if (e.key === 'ArrowUp') { e.preventDefault(); if (keyInputReversed) { stageNextSuggestion(); } else { stagePrevSuggestion(); } } else if (e.key === 'Enter') { e.preventDefault(); selectStagedSuggestion(); } else if (e.key === 'Escape') { e.preventDefault(); reset(); } // else -> propagates up to allow input } return (() => { var _el$ = _tmpl$(), _el$2 = _el$.firstChild; addEventListener(_el$2, "keydown", handleKeyDown); addEventListener(_el$2, "input", handleInput); insert(_el$, (() => { var _c$ = memo(() => numSuggestions() > 0); return () => _c$() && (() => { var _el$3 = _tmpl$2(); insert(_el$3, () => suggestions().map((s, i) => (() => { var _el$4 = _tmpl$3(); addEventListener(_el$4, "click", selectStagedSuggestion); addEventListener(_el$4, "mouseenter", () => stageSuggestion(i)); insert(_el$4, () => props.renderSuggestion(s)); effect(_p$ => { var _v$3 = staged() === i, _v$4 = staged() === i ? 'true' : 'false'; _v$3 !== _p$.e && setAttribute(_el$4, "data-staged", _p$.e = _v$3); _v$4 !== _p$.t && setAttribute(_el$4, "aria-selected", _p$.t = _v$4); return _p$; }, { e: undefined, t: undefined }); return _el$4; })())); return _el$3; })(); })(), null); effect(_p$ => { var _v$ = numSuggestions() > 0, _v$2 = props.placeholder ?? ''; _v$ !== _p$.e && setAttribute(_el$, "aria-expanded", _p$.e = _v$); _v$2 !== _p$.t && setAttribute(_el$2, "placeholder", _p$.t = _v$2); return _p$; }, { e: undefined, t: undefined }); effect(() => _el$2.value = query()); return _el$; })(); } export { Suggest as default }; //# sourceMappingURL=index.js.map