@retailmenot/anchor
Version:
A React UI Library by RetailMeNot
171 lines (168 loc) • 7.63 kB
JavaScript
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