react-input-suggestions
Version:
A React input component with pluggable suggestions and autocomplete
60 lines (50 loc) • 3.63 kB
JavaScript
import{useEffect as S,useState as k}from"react";var E="ArrowDown",R="ArrowUp",C="Enter",K="Tab";var T=(t,n,o)=>{let[r,a]=k(!1);S(()=>{a(Boolean(t&&t.current&&t.current.value.length>0&&o.length>0))},[t,o]),S(()=>{let e=s=>{r&&!n.current?.contains(s.target)&&a(!1)};return n.current?.querySelectorAll("li")?.forEach(s=>{s.firstChild.tabIndex=0}),document.addEventListener("mousedown",e),()=>{document.removeEventListener("mousedown",e)}},[n,r]);let u=e=>{n.current?.querySelector(`li:${e}-of-type`)?.firstChild?.focus()},d=()=>n.current?.querySelector("li > *:focus"),g=e=>{e.currentTarget.value&&!d()&&[E,R].includes(e.key)&&(e.preventDefault(),e.key===E&&u("first"),e.key===R&&u("last"))},p=e=>{e?.currentTarget?.firstChild?.focus()},y=(e,s)=>{e.preventDefault();let m=e.currentTarget?.[`${s}`]?.firstChild;m?m.focus():u(s==="nextSibling"?"first":"last")},c=(e,s)=>{y(e,s)};return{selectInitialResult:g,onResultsHover:p,onResultsKeyDown:e=>{[E,K].includes(e.key)?c(e,"nextSibling"):e.key===R?c(e,"previousSibling"):e.key!==C&&t.current?.focus()},showSuggestions:r,setShowSuggestions:a,onInputFocus:e=>{document.activeElement===t.current&&e.currentTarget.value!==""&&a(!0)}}};import{useRef as w,useState as U}from"react";import N,{Children as A,cloneElement as D}from"react";import F from"react-string-replace";import{jsx as O}from"react/jsx-runtime";var l={get:t=>{if(["string","number"].includes(typeof t))return t;if(t instanceof Array)return[...new Set(t.map(l.get))].join(" ");if(typeof t=="object"&&t)return l.get(t.props.children)},highlightKeyword:(t,n)=>F(t,n,(o,r)=>O("mark",{children:o},r)),cloneChildren:(t,n)=>A.map(t,o=>o.props?D(o,{children:l.highlightKeyword(l.cloneChildren(o.props.children,n),n)}):o),wrap:(t,n)=>{let o=t,{props:{children:r}}=o;return N.cloneElement(o,{children:typeof r=="string"?l.highlightKeyword(r,n):l.cloneChildren(r,n)})}};import P from"@emotion/styled";var v=P.div`
position: relative;
input {
width: 100%;
}
mark {
display: inline;
padding: 0;
}
ul {
position: absolute;
top: 100%;
width: 100%;
box-sizing: border-box;
list-style-type: none;
overflow-y: auto;
li > * {
display: block;
cursor: pointer;
text-decoration: none;
&:focus {
border: 0;
box-shadow: 0;
outline: 0;
}
}
}
${({withTheme:t})=>t&&`input {
font-size: 1rem;
padding: 20px;
border: 1px solid #dadada;
background: #efefef;
width: 100%;
outline: 0;
}
ul {
padding: 0;
margin-top: -3px;
border: 1px solid #dadada;
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.2);
font-size: 1rem;
li > * {
padding: 1rem;
&:focus {
background: #efefef;
}
}
}
`}
`;import{jsx as h,jsxs as X}from"react/jsx-runtime";var q=({suggestions:t,type:n="search",name:o="q",placeholder:r="Search",autoFocus:a=!1,className:u="",withTheme:d=!1,id:g,onChange:p,highlightKeywords:y=!1})=>{let[c,b]=U(t),f=w(null),e=w(null),{selectInitialResult:s,onResultsHover:m,onResultsKeyDown:L,showSuggestions:I,onInputFocus:x}=T(f,e,c),M=i=>b(t.filter(H=>l.get(H)?.toLowerCase().includes(i.target.value.toLowerCase()||"")));return X(v,{id:g,className:u,withTheme:d,children:[h("input",{ref:f,type:n,name:o,placeholder:r,autoFocus:a,onChange:i=>{p&&p(i),M(i)},onKeyDown:s,onFocus:x,spellCheck:!1,autoComplete:"off",autoCapitalize:"off"}),I&&h("ul",{ref:e,children:c.map(i=>h("li",{onMouseOver:m,onKeyDown:L,children:y?l.wrap(i,f.current?.value||""):i},l.get(i)))})]})},z=q;export{z as InputSuggestions,T as useSuggestions};