UNPKG

@retailmenot/anchor

Version:

A React UI Library by RetailMeNot

626 lines (549 loc) 20.6 kB
import { c as _slicedToArray, e as _typeof, a as _taggedTemplateLiteral } from './anchor-chunk-7b9d8557.js'; import { a as __rest } from './anchor-chunk-27f34e54.js'; import { Input } from './form.js'; import { List } from './list.js'; import { forwardRef, createElement, useContext, useState, Children, Fragment, useEffect, useRef, createRef, Component, cloneElement, useReducer, PureComponent, isValidElement, createContext, useImperativeHandle } from 'react'; import classNames from 'classnames'; import styled, { css } from '@xstyled/styled-components'; import { th, space } from '@xstyled/system'; import './anchor-chunk-5b0bbe0b.js'; import './anchor-chunk-cd7ef49a.js'; import './anchor-chunk-20e4020f.js'; import './anchor-chunk-6ebffda8.js'; import './anchor-chunk-210b63ef.js'; import './addevent.js'; import './anchor-chunk-25c07228.js'; import './arrowback.js'; import './arrowforward.js'; import './avataricon.js'; import './avataroutline.js'; import './barcode.js'; import './bulletlist.js'; import './calendar.js'; import './camera.js'; import './cart.js'; import './cashback.js'; import './cells.js'; import './chat.js'; import './check.js'; import './checksmall.js'; import './chevrondown.js'; import './chevrondownsmall.js'; import './chevronleft.js'; import './chevronleftsmall.js'; import './chevronright.js'; import './chevronrightsmall.js'; import './chevronup.js'; import './chevronupsmall.js'; import './clock.js'; import './close.js'; import './closesmall.js'; import './commentmore.js'; import './creditcard.js'; import './crosshairs.js'; import './cut.js'; import './disabled.js'; import './dislike.js'; import './download.js'; import './ellipses.js'; import './ellipsesvertical.js'; import './envelope.js'; import './envelopeopen.js'; import './error.js'; import './expand.js'; import './gear.js'; import './giftcard.js'; import './hamburger.js'; import './heart.js'; import './heartoutline.js'; import './home.js'; import './info.js'; import './infooutline.js'; import './laptop.js'; import './lightning.js'; import './like.js'; import './listicon.js'; import './lock.js'; import './map.js'; import './marker.js'; import './markeroutline.js'; import './mobile.js'; import './news.js'; import './pencil.js'; import './play.js'; import './plus.js'; import './plussmall.js'; import './print.js'; import './question.js'; import './questionoutline.js'; import './refresh.js'; import './retailmenotlogo.js'; import './sadface.js'; import './search.js'; import './share.js'; import './sliders.js'; import './star.js'; import './starhalf.js'; import './staroutline.js'; import './success.js'; import './successoutline.js'; import './tag.js'; import './upload.js'; import './tagadd.js'; function _templateObject() { var data = _taggedTemplateLiteral(["\n background-color: white;\n position: absolute;\n width: inherit;\n z-index: 3;\n box-sizing: border-box;\n box-shadow: 0 0.5rem 0.75rem -0.375rem rgba(0, 0, 0, 0.2);\n border-radius: 0 0 ", " ", ";\n padding: 1rem;\n ", ";\n"]); _templateObject = function _templateObject() { return data; }; return data; } var useEffect$1 = useEffect, useState$1 = useState, forwardRef$1 = forwardRef, useImperativeHandle$1 = useImperativeHandle; var ResultContainerSpaceFromAutoComplete = { sm: '2.6rem', md: '3.25rem', lg: '3.25rem' }; var StyledResultsContainer = styled('div')(_templateObject(), th.radius('base'), th.radius('base'), function (_ref) { var _ref$size = _ref.size, size = _ref$size === void 0 ? 'md' : _ref$size; return css({ top: ResultContainerSpaceFromAutoComplete[size] }); }); var createResult = function createResult(label, value) { return { label: label, value: value || label }; }; var generateResults = function generateResults(results, currentIndex, setCurrentIndex, emitSelectedItem, term, setTerm) { var cleanResults = []; if (Array.isArray(results) && results.length) { // Generic arrays are converted to DataItems if (typeof results[0] === 'string') { results.forEach(function (result, index) { var itemIndex = relativeIndex(index); cleanResults.push(createResult(result, { label: result || '', // Add one to index so that the input is an index active: itemIndex === currentIndex, key: "anchor-result-".concat(itemIndex), onMouseOver: function onMouseOver() { setCurrentIndex(itemIndex); setTerm(term); }, onSelect: function onSelect() { return emitSelectedItem({ label: result, value: { label: result, value: result } }); } })); }); } else if (_typeof(results[0]) === 'object') { results.forEach(function (_a, index) { var _a$label = _a.label, label = _a$label === void 0 ? '' : _a$label, props = __rest(_a, ["label"]); var itemIndex = relativeIndex(index); cleanResults.push(createResult(label, Object.assign(Object.assign({ label: label }, props), { // Add one to index so that the input is an index active: itemIndex === currentIndex, key: "anchor-result-".concat(itemIndex), onMouseOver: function onMouseOver() { setCurrentIndex(itemIndex); setTerm(term); }, onSelect: function onSelect() { return emitSelectedItem({ label: label, value: Object.assign({ label: label }, props) }); } }))); }); } } return cleanResults; }; /* Entire List: [ 0, 1, 2, 3, 4 ] Iterable List: [ 1, 3, 4 ] forward: - go to next iterable index - unless at max; go to 0 (assign initialTerm) backward: - go to previous iterable index - unless at 0; go to max (assign initialTerm) both - store initialTerm - Emit initialTerm or currentLabel - Update currentIndex =================================================== getNext(currentTerm: string, max = iterableList.length - 1, min = 0, currentIndex, direction): number emitTermAndUpdateIndex(newIndex, values): void • updateIndex(emitActiveIndex, setCurrentIndex): void • updateTerm(emitActiveIndex, setCurrentIndex): void */ var relativeIndex = function relativeIndex(index) { return index + 1; }; var ResultsContainer = forwardRef$1(function (_a, resultsContainerRef) { var className = _a.className, _a$dataSource = _a.dataSource, dataSource = _a$dataSource === void 0 ? [] : _a$dataSource, emitSelectedItem = _a.emitSelectedItem, emitActiveTerm = _a.emitActiveTerm, highlightFirst = _a.highlightFirst, term = _a.term, resultTemplate = _a.resultTemplate, props = __rest(_a, ["className", "dataSource", "emitSelectedItem", "emitActiveTerm", "highlightFirst", "term", "resultTemplate"]); var _useState = useState$1(0), _useState2 = _slicedToArray(_useState, 2), currentIndex = _useState2[0], setCurrentIndex = _useState2[1]; var _useState3 = useState$1(''), _useState4 = _slicedToArray(_useState3, 2), initialTerm = _useState4[0], setInitialTerm = _useState4[1]; var results = generateResults(dataSource, currentIndex, setCurrentIndex, emitSelectedItem, term, setInitialTerm); useEffect$1(function () { // Check if current term exists in results and capture the index var termIndex; if (term) { termIndex = relativeIndex(results.findIndex(function (result) { return result.label === term; })); } // If highlightFirst is true and term does not exist in results // update termIndex to the first result if (highlightFirst && !termIndex) { // If first item is a title, skip to next item termIndex = results[0].value.listItemType === 'title' ? 2 : 1; } // Set term index if it exists along with initial term if (termIndex) { setCurrentIndex(termIndex); setInitialTerm(term ? term : results[termIndex - 1].label); } }, []); var iterativeIndexes = [0]; results.forEach(function (_ref2, index) { var listItemType = _ref2.value.listItemType; if (!listItemType || listItemType === 'item') { // Add one to index to allow input to be item 0 iterativeIndexes.push(relativeIndex(index)); } }); var traverse = function traverse(range, index, forward) { // Get the max range; the minimum index of any array is 0 var max = range.length - 1; // Determine the actual index relative to all iterable values var indexRelative = range.indexOf(index); // Increment/decrement accordingly var nextIndex = forward ? indexRelative + 1 : indexRelative - 1; // For 'next' iteration, reset to 0 if at max if (forward && nextIndex > max) { nextIndex = 0; } // For 'prev' iteration, reset to max if at 0 if (!forward && nextIndex < 0) { nextIndex = max; } return nextIndex; }; var handleTraversal = function handleTraversal() { var currentTerm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var forward = arguments.length > 1 ? arguments[1] : undefined; // Check if there's a initialTerm if (!initialTerm || initialTerm === '') { // If there's no initialTerm, assign the current input value setInitialTerm(currentTerm); } // Determine the next index var nextIndex = traverse(iterativeIndexes, currentIndex, forward); // Assign that index locally setCurrentIndex(iterativeIndexes[nextIndex]); if (nextIndex === 0) { // Emit the user's initial term emitActiveTerm(initialTerm); setInitialTerm(''); } else { // Emit the active term, but subtract one bc the input is not part of the original data set emitActiveTerm(results[iterativeIndexes[nextIndex] - 1].label); } }; var isInRange = currentIndex >= 0 && currentIndex <= results.length; // If currentIndex is 0, it's referring to the input field var adjustedForInputIndex = currentIndex ? currentIndex - 1 : 0; // Check for a title item index and skip it var checkForTitleIndex = function checkForTitleIndex(index) { var shouldBumpIndex = results[index].value.listItemType === 'title' && // Check if title is last result index < results.length; return shouldBumpIndex ? index + 1 : index; }; useImperativeHandle$1(resultsContainerRef, function () { return { setActiveIndex: function setActiveIndex(itemIndex) { setCurrentIndex(highlightFirst ? relativeIndex(checkForTitleIndex(itemIndex)) : itemIndex); }, clearInitialTerm: function clearInitialTerm() { setInitialTerm(''); }, handleNext: function handleNext(currentTerm) { handleTraversal(currentTerm, true); }, updateTerm: function updateTerm(currentTerm) { if (isInRange) { emitActiveTerm(results[checkForTitleIndex(adjustedForInputIndex)].label); } }, handlePrevious: function handlePrevious(currentTerm) { handleTraversal(currentTerm, false); }, selectActive: function selectActive() { if (isInRange) { emitSelectedItem(results[adjustedForInputIndex]); } } }; }); return createElement(StyledResultsContainer, Object.assign({ className: classNames('anchor-auto-complete-results-container', className) }, props), resultTemplate ? createElement("div", { className: "auto-complete-results" }, results.map(function (_ref3, index) { var label = _ref3.label, value = _ref3.value; return createElement(resultTemplate, { label: label, value: value, term: term, currentIndex: currentIndex, index: relativeIndex(index), key: relativeIndex(index) }); })) : createElement(List, { items: results, className: "auto-complete-results" })); }); function _templateObject$1() { var data = _taggedTemplateLiteral(["\n width: 100%;\n position: relative;\n border: solid thin transparent;\n ", ";\n border-radius: base;\n transition: border-color 250ms;\n ", "\n ", ";\n\n &:hover {\n ", ";\n }\n\n &:active,\n &.focus,\n &:focus {\n ", ";\n }\n\n .auto-complete-input {\n border: none;\n }\n ", "\n"]); _templateObject$1 = function _templateObject() { return data; }; return data; } var useEffect$2 = useEffect, useState$2 = useState, useRef$1 = useRef; var EventKeyCodes = { TAB: 9, ENTER: 13, ARROW_UP: 38, ARROW_DOWN: 40 }; var StyledAutoComplete = styled('div')(_templateObject$1(), function (_ref) { var border = _ref.border; return css({ borderColor: border ? 'borders.base' : 'transparent' }); }, function (_ref2) { var backgroundColor = _ref2.background, color = _ref2.color; return css({ backgroundColor: backgroundColor, color: color }); }, function (_ref3) { var shadow = _ref3.shadow; return shadow ? 'box-shadow: 0 0.5rem 0.75rem -0.375rem rgba(0, 0, 0, 0.12);' : null; }, function (_ref4) { var border = _ref4.border; return css({ borderColor: border ? 'borders.dark' : 'transparent' }); }, function (_ref5) { var border = _ref5.border; return css({ borderColor: border ? 'borders.dark' : 'transparent' }); }, space); var AutoComplete = function AutoComplete(_a) { var allowClear = _a.allowClear, _a$browserAutoComplet = _a.browserAutoComplete, browserAutoComplete = _a$browserAutoComplet === void 0 ? false : _a$browserAutoComplet, _a$autoFocus = _a.autoFocus, autoFocus = _a$autoFocus === void 0 ? false : _a$autoFocus, _a$background = _a.background, background = _a$background === void 0 ? 'white' : _a$background, _a$border = _a.border, border = _a$border === void 0 ? true : _a$border, className = _a.className, _a$color = _a.color, color = _a$color === void 0 ? 'text.light' : _a$color, _a$dataSource = _a.dataSource, dataSource = _a$dataSource === void 0 ? [] : _a$dataSource, _a$debug = _a.debug, debug = _a$debug === void 0 ? false : _a$debug, _a$highlightFirst = _a.highlightFirst, highlightFirst = _a$highlightFirst === void 0 ? false : _a$highlightFirst, inputProps = _a.inputProps, _a$inputType = _a.inputType, inputType = _a$inputType === void 0 ? 'text' : _a$inputType, _a$name = _a.name, name = _a$name === void 0 ? '' : _a$name, _a$onChange = _a.onChange, onChange = _a$onChange === void 0 ? function () { return null; } : _a$onChange, _a$onFilter = _a.onFilter, onFilter = _a$onFilter === void 0 ? function () { return null; } : _a$onFilter, _a$onSelect = _a.onSelect, onSelect = _a$onSelect === void 0 ? function () { return null; } : _a$onSelect, placeholder = _a.placeholder, prefix = _a.prefix, resultTemplate = _a.resultTemplate, _a$shadow = _a.shadow, shadow = _a$shadow === void 0 ? false : _a$shadow, _a$size = _a.size, size = _a$size === void 0 ? 'lg' : _a$size, suffix = _a.suffix, _a$value = _a.value, value = _a$value === void 0 ? '' : _a$value, 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 var _useState = useState$2(autoFocus), _useState2 = _slicedToArray(_useState, 2), isFocused = _useState2[0], setIsFocused = _useState2[1]; // The current search term var _useState3 = useState$2(value ? "".concat(value) : ''), _useState4 = _slicedToArray(_useState3, 2), term = _useState4[0], setTerm = _useState4[1]; var autoFocusRef = useRef$1(null); // Instance of the nested input var inputRef = useRef$1(); var resultsRef = useRef$1(); var rootElement; // Handle click outside of auto complete component var handleOutsideClick = function handleOutsideClick(e) { if (isFocused && autoFocusRef.current && !autoFocusRef.current.contains(e.target)) { setIsFocused(false); } }; // Set root element and listener for outside click useEffect$2(function () { rootElement = autoFocusRef.current ? autoFocusRef.current.getRootNode() : null; if (rootElement) { rootElement.addEventListener('click', handleOutsideClick); } return function () { return rootElement.removeEventListener('click', handleOutsideClick); }; }, [inputRef, isFocused]); // Handle updating the search term var changeSearchTerm = function changeSearchTerm(newTerm) { // Fire external filter event onFilter(newTerm); // Update new term setTerm(newTerm); }; // Handle updating the search term var updateInputAndTerm = function updateInputAndTerm(newTerm) { // Update the filter setTerm(newTerm); // Update the input inputRef.current.update(newTerm); }; // Handle updating the autocomplete value var changeActiveValue = function 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 createElement(StyledAutoComplete, Object.assign({ ref: autoFocusRef, shadow: shadow, border: border, background: background, color: color, className: classNames('anchor-auto-complete', className, { focus: isFocused }) }, props), createElement(Input, { ariaLabel: name.length ? "auto-complete-".concat(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: function onFocus() { return setIsFocused(true); }, onChange: function onChange(newValue) { changeSearchTerm(newValue); }, onKeyDown: function 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 && createElement(ResultsContainer, { size: size, ref: resultsRef, emitSelectedItem: function emitSelectedItem(item) { changeActiveValue(item); }, emitActiveTerm: function emitActiveTerm(newTerm) { updateInputAndTerm(newTerm); }, resultTemplate: resultTemplate, dataSource: dataSource, term: term, highlightFirst: highlightFirst })); }; export { AutoComplete }; //# sourceMappingURL=autocomplete.js.map