UNPKG

@retailmenot/anchor

Version:

A React UI Library by RetailMeNot

633 lines (553 loc) 21 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var __chunk_1 = require('./anchor-chunk-24f232e7.js'); var __chunk_2 = require('./anchor-chunk-9d9a5df6.js'); var form = require('./form.js'); var list = require('./list.js'); var React = require('react'); var classNames = _interopDefault(require('classnames')); var styled = require('@xstyled/styled-components'); var styled__default = _interopDefault(styled); var system = require('@xstyled/system'); require('./anchor-chunk-cd6fece5.js'); require('./anchor-chunk-1efd6395.js'); require('./anchor-chunk-598e53e1.js'); require('./anchor-chunk-f296150d.js'); require('./anchor-chunk-925bd1f9.js'); require('./addevent.js'); require('./anchor-chunk-31a3b978.js'); require('./arrowback.js'); require('./arrowforward.js'); require('./avataricon.js'); require('./avataroutline.js'); require('./barcode.js'); require('./bulletlist.js'); require('./calendar.js'); require('./camera.js'); require('./cart.js'); require('./cashback.js'); require('./cells.js'); require('./chat.js'); require('./check.js'); require('./checksmall.js'); require('./chevrondown.js'); require('./chevrondownsmall.js'); require('./chevronleft.js'); require('./chevronleftsmall.js'); require('./chevronright.js'); require('./chevronrightsmall.js'); require('./chevronup.js'); require('./chevronupsmall.js'); require('./clock.js'); require('./close.js'); require('./closesmall.js'); require('./commentmore.js'); require('./creditcard.js'); require('./crosshairs.js'); require('./cut.js'); require('./disabled.js'); require('./dislike.js'); require('./download.js'); require('./ellipses.js'); require('./ellipsesvertical.js'); require('./envelope.js'); require('./envelopeopen.js'); require('./error.js'); require('./expand.js'); require('./gear.js'); require('./giftcard.js'); require('./hamburger.js'); require('./heart.js'); require('./heartoutline.js'); require('./home.js'); require('./info.js'); require('./infooutline.js'); require('./laptop.js'); require('./lightning.js'); require('./like.js'); require('./listicon.js'); require('./lock.js'); require('./map.js'); require('./marker.js'); require('./markeroutline.js'); require('./mobile.js'); require('./news.js'); require('./pencil.js'); require('./play.js'); require('./plus.js'); require('./plussmall.js'); require('./print.js'); require('./question.js'); require('./questionoutline.js'); require('./refresh.js'); require('./retailmenotlogo.js'); require('./sadface.js'); require('./search.js'); require('./share.js'); require('./sliders.js'); require('./star.js'); require('./starhalf.js'); require('./staroutline.js'); require('./success.js'); require('./successoutline.js'); require('./tag.js'); require('./upload.js'); require('./tagadd.js'); function _templateObject() { var data = __chunk_1._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 = React.useEffect, useState = React.useState, forwardRef = React.forwardRef, useImperativeHandle = React.useImperativeHandle; var ResultContainerSpaceFromAutoComplete = { sm: '2.6rem', md: '3.25rem', lg: '3.25rem' }; var StyledResultsContainer = styled__default('div')(_templateObject(), system.th.radius('base'), system.th.radius('base'), function (_ref) { var _ref$size = _ref.size, size = _ref$size === void 0 ? 'md' : _ref$size; return styled.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 (__chunk_1._typeof(results[0]) === 'object') { results.forEach(function (_a, index) { var _a$label = _a.label, label = _a$label === void 0 ? '' : _a$label, props = __chunk_2.__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(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 = __chunk_2.__rest(_a, ["className", "dataSource", "emitSelectedItem", "emitActiveTerm", "highlightFirst", "term", "resultTemplate"]); var _useState = useState(0), _useState2 = __chunk_1._slicedToArray(_useState, 2), currentIndex = _useState2[0], setCurrentIndex = _useState2[1]; var _useState3 = useState(''), _useState4 = __chunk_1._slicedToArray(_useState3, 2), initialTerm = _useState4[0], setInitialTerm = _useState4[1]; var results = generateResults(dataSource, currentIndex, setCurrentIndex, emitSelectedItem, term, setInitialTerm); useEffect(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(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 React.createElement(StyledResultsContainer, Object.assign({ className: classNames('anchor-auto-complete-results-container', className) }, props), resultTemplate ? React.createElement("div", { className: "auto-complete-results" }, results.map(function (_ref3, index) { var label = _ref3.label, value = _ref3.value; return React.createElement(resultTemplate, { label: label, value: value, term: term, currentIndex: currentIndex, index: relativeIndex(index), key: relativeIndex(index) }); })) : React.createElement(list.List, { items: results, className: "auto-complete-results" })); }); function _templateObject$1() { var data = __chunk_1._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$1 = React.useEffect, useState$1 = React.useState, useRef = React.useRef; var EventKeyCodes = { TAB: 9, ENTER: 13, ARROW_UP: 38, ARROW_DOWN: 40 }; var StyledAutoComplete = styled__default('div')(_templateObject$1(), function (_ref) { var border = _ref.border; return styled.css({ borderColor: border ? 'borders.base' : 'transparent' }); }, function (_ref2) { var backgroundColor = _ref2.background, color = _ref2.color; return styled.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 styled.css({ borderColor: border ? 'borders.dark' : 'transparent' }); }, function (_ref5) { var border = _ref5.border; return styled.css({ borderColor: border ? 'borders.dark' : 'transparent' }); }, system.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 = __chunk_2.__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$1(autoFocus), _useState2 = __chunk_1._slicedToArray(_useState, 2), isFocused = _useState2[0], setIsFocused = _useState2[1]; // The current search term var _useState3 = useState$1(value ? "".concat(value) : ''), _useState4 = __chunk_1._slicedToArray(_useState3, 2), term = _useState4[0], setTerm = _useState4[1]; var autoFocusRef = useRef(null); // Instance of the nested input var inputRef = useRef(); var resultsRef = useRef(); 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$1(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 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(form.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 && React.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 })); }; exports.AutoComplete = AutoComplete; //# sourceMappingURL=autocomplete.js.map