UNPKG

@retailmenot/anchor

Version:

A React UI Library by RetailMeNot

171 lines (168 loc) 7.63 kB
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; }; // REACT import * as React from 'react'; // VENDOR import classNames from 'classnames'; import styled, { css } from '@xstyled/styled-components'; import { space as spaceStyles } from '@xstyled/system'; // COMPONENTS import { Input } from '../Form'; import { ResultsContainer } from './ResultsContainer'; const { useEffect, useState, useRef } = React; const EventKeyCodes = { TAB: 9, ENTER: 13, ARROW_UP: 38, ARROW_DOWN: 40, }; const StyledAutoComplete = styled('div') ` width: 100%; position: relative; border: solid thin transparent; ${({ border }) => css({ borderColor: border ? 'borders.base' : 'transparent', })}; border-radius: base; transition: border-color 250ms; ${({ background: backgroundColor, color }) => css({ backgroundColor, color })} ${({ shadow }) => shadow ? 'box-shadow: 0 0.5rem 0.75rem -0.375rem rgba(0, 0, 0, 0.12);' : null}; &:hover { ${({ border }) => css({ borderColor: border ? 'borders.dark' : 'transparent', })}; } &:active, &.focus, &:focus { ${({ border }) => css({ borderColor: border ? 'borders.dark' : 'transparent', })}; } .auto-complete-input { border: none; } ${spaceStyles} `; export const AutoComplete = (_a) => { var { allowClear, browserAutoComplete = false, autoFocus = false, background = 'white', border = true, className, color = 'text.light', dataSource = [], debug = false, highlightFirst = false, inputProps, inputType = 'text', name = '', onChange = () => null, onFilter = () => null, onSelect = () => null, placeholder, prefix, resultTemplate, shadow = false, size = 'lg', suffix, value = '' } = _a, props = __rest(_a, ["allowClear", "browserAutoComplete", "autoFocus", "background", "border", "className", "color", "dataSource", "debug", "highlightFirst", "inputProps", "inputType", "name", "onChange", "onFilter", "onSelect", "placeholder", "prefix", "resultTemplate", "shadow", "size", "suffix", "value"]); // Flag for autocomplete focus const [isFocused, setIsFocused] = useState(autoFocus); // The current search term const [term, setTerm] = useState(value ? `${value}` : ''); const autoFocusRef = useRef(null); // Instance of the nested input const inputRef = useRef(); const resultsRef = useRef(); let rootElement; // Handle click outside of auto complete component const handleOutsideClick = (e) => { if (isFocused && autoFocusRef.current && !autoFocusRef.current.contains(e.target)) { setIsFocused(false); } }; // Set root element and listener for outside click useEffect(() => { rootElement = autoFocusRef.current ? autoFocusRef.current.getRootNode() : null; if (rootElement) { rootElement.addEventListener('click', handleOutsideClick); } return () => rootElement.removeEventListener('click', handleOutsideClick); }, [inputRef, isFocused]); // Handle updating the search term const changeSearchTerm = (newTerm) => { // Fire external filter event onFilter(newTerm); // Update new term setTerm(newTerm); }; // Handle updating the search term const updateInputAndTerm = (newTerm) => { // Update the filter setTerm(newTerm); // Update the input inputRef.current.update(newTerm); }; // Handle updating the autocomplete value const changeActiveValue = (newValue) => { changeSearchTerm(newValue.label); // Fire External Select/Change Event onSelect(newValue.label, newValue); onChange(newValue.label, newValue); // Update the input value inputRef.current.update(newValue.label); inputRef.current.blur(); // Reset the index resultsRef.current.setActiveIndex(0); setIsFocused(false); }; return (React.createElement(StyledAutoComplete, Object.assign({ ref: autoFocusRef, shadow: shadow, border: border, background: background, color: color, className: classNames('anchor-auto-complete', className, { focus: isFocused, }) }, props), React.createElement(Input, { ariaLabel: name.length ? `auto-complete-${name.toLowerCase()}` : 'auto-complete', inputProps: inputProps, type: inputType, value: term, ref: inputRef, size: size, autoComplete: browserAutoComplete ? 'on' : 'off', autoFocus: autoFocus, prefix: prefix, suffix: suffix, placeholder: placeholder, onFocus: () => setIsFocused(true), onChange: (newValue) => { changeSearchTerm(newValue); }, onKeyDown: (event) => { switch (event.keyCode) { case EventKeyCodes.TAB: // This should only fire if the results container exists if (isFocused && resultsRef.current) { // Update term of the autocomplete resultsRef.current.updateTerm(term); setIsFocused(false); } break; case EventKeyCodes.ENTER: if (term) { event.preventDefault(); } event.stopPropagation(); // This should only fire if the results container exists if (isFocused && resultsRef.current) { // Unset initialTerm resultsRef.current.clearInitialTerm(); // Set the active value of the autocomplete resultsRef.current.selectActive(); } break; case EventKeyCodes.ARROW_DOWN: event.preventDefault(); event.stopPropagation(); resultsRef.current.handleNext(term); break; case EventKeyCodes.ARROW_UP: event.preventDefault(); event.stopPropagation(); resultsRef.current.handlePrevious(term); break; default: if (resultsRef.current) { resultsRef.current.clearInitialTerm(); resultsRef.current.setActiveIndex(0); } break; } }, name: "auto-complete", className: "auto-complete-input" }), (isFocused || debug) && dataSource.length > 0 && (React.createElement(ResultsContainer, { size: size, ref: resultsRef, emitSelectedItem: (item) => { changeActiveValue(item); }, emitActiveTerm: (newTerm) => { updateInputAndTerm(newTerm); }, resultTemplate: resultTemplate, dataSource: dataSource, term: term, highlightFirst: highlightFirst })))); }; //# sourceMappingURL=AutoComplete.component.js.map