{"version":3,"file":"search-module-G2LPJzpV.js","sources":["../../../../node_modules/core-js/internals/validate-arguments-length.js","../../../../node_modules/core-js/modules/web.url-search-params.delete.js","../../../../node_modules/core-js/modules/web.url-search-params.has.js","../../../../node_modules/core-js/modules/web.url-search-params.size.js","../../../../src/main/js/navigation/global/components/search/searchMetrics.js","../../../../src/main/js/navigation/global/components/search/search.js","../../../../src/main/js/navigation/global/components/search/filterResults.js","../../../../src/main/js/navigation/global/components/search/constants.js","../../../../src/main/js/navigation/global/components/search/listboxUtil.js","../../../../src/main/js/navigation/global/components/search/suggest.js","../../../../src/main/js/navigation/global/components/search/recents.js","../../../../src/main/js/navigation/global/components/search/entry/main.js","../../../../src/main/js/navigation/global/components/search/entry/search-module.js"],"sourcesContent":["'use strict';\nvar $TypeError = TypeError;\n\nmodule.exports = function (passed, required) {\n if (passed < required) throw new $TypeError('Not enough arguments');\n return passed;\n};\n","'use strict';\nvar defineBuiltIn = require('../internals/define-built-in');\nvar uncurryThis = require('../internals/function-uncurry-this');\nvar toString = require('../internals/to-string');\nvar validateArgumentsLength = require('../internals/validate-arguments-length');\n\nvar $URLSearchParams = URLSearchParams;\nvar URLSearchParamsPrototype = $URLSearchParams.prototype;\nvar append = uncurryThis(URLSearchParamsPrototype.append);\nvar $delete = uncurryThis(URLSearchParamsPrototype['delete']);\nvar forEach = uncurryThis(URLSearchParamsPrototype.forEach);\nvar push = uncurryThis([].push);\nvar params = new $URLSearchParams('a=1&a=2&b=3');\n\nparams['delete']('a', 1);\n// `undefined` case is a Chromium 117 bug\n// https://bugs.chromium.org/p/v8/issues/detail?id=14222\nparams['delete']('b', undefined);\n\nif (params + '' !== 'a=2') {\n defineBuiltIn(URLSearchParamsPrototype, 'delete', function (name /* , value */) {\n var length = arguments.length;\n var $value = length < 2 ? undefined : arguments[1];\n if (length && $value === undefined) return $delete(this, name);\n var entries = [];\n forEach(this, function (v, k) { // also validates `this`\n push(entries, { key: k, value: v });\n });\n validateArgumentsLength(length, 1);\n var key = toString(name);\n var value = toString($value);\n var index = 0;\n var dindex = 0;\n var found = false;\n var entriesLength = entries.length;\n var entry;\n while (index < entriesLength) {\n entry = entries[index++];\n if (found || entry.key === key) {\n found = true;\n $delete(this, entry.key);\n } else dindex++;\n }\n while (dindex < entriesLength) {\n entry = entries[dindex++];\n if (!(entry.key === key && entry.value === value)) append(this, entry.key, entry.value);\n }\n }, { enumerable: true, unsafe: true });\n}\n","'use strict';\nvar defineBuiltIn = require('../internals/define-built-in');\nvar uncurryThis = require('../internals/function-uncurry-this');\nvar toString = require('../internals/to-string');\nvar validateArgumentsLength = require('../internals/validate-arguments-length');\n\nvar $URLSearchParams = URLSearchParams;\nvar URLSearchParamsPrototype = $URLSearchParams.prototype;\nvar getAll = uncurryThis(URLSearchParamsPrototype.getAll);\nvar $has = uncurryThis(URLSearchParamsPrototype.has);\nvar params = new $URLSearchParams('a=1');\n\n// `undefined` case is a Chromium 117 bug\n// https://bugs.chromium.org/p/v8/issues/detail?id=14222\nif (params.has('a', 2) || !params.has('a', undefined)) {\n defineBuiltIn(URLSearchParamsPrototype, 'has', function has(name /* , value */) {\n var length = arguments.length;\n var $value = length < 2 ? undefined : arguments[1];\n if (length && $value === undefined) return $has(this, name);\n var values = getAll(this, name); // also validates `this`\n validateArgumentsLength(length, 1);\n var value = toString($value);\n var index = 0;\n while (index < values.length) {\n if (values[index++] === value) return true;\n } return false;\n }, { enumerable: true, unsafe: true });\n}\n","'use strict';\nvar DESCRIPTORS = require('../internals/descriptors');\nvar uncurryThis = require('../internals/function-uncurry-this');\nvar defineBuiltInAccessor = require('../internals/define-built-in-accessor');\n\nvar URLSearchParamsPrototype = URLSearchParams.prototype;\nvar forEach = uncurryThis(URLSearchParamsPrototype.forEach);\n\n// `URLSearchParams.prototype.size` getter\n// https://github.com/whatwg/url/pull/734\nif (DESCRIPTORS && !('size' in URLSearchParamsPrototype)) {\n defineBuiltInAccessor(URLSearchParamsPrototype, 'size', {\n get: function size() {\n var count = 0;\n forEach(this, function () { count++; });\n return count;\n },\n configurable: true,\n enumerable: true\n });\n}\n","import { analyticsCustomClickHandler } from '../../../../_shared-components/navigationMetrics';\n\nconst conditionalAnalytics = {\n searchHistory: {\n searchTermType: 'autosuggest-recentquery',\n linkName: 'searchbox-_-text-_-autosuggest',\n searchRecentQuerySource: 'product',\n },\n autosuggest: {\n searchTermType: 'autosuggest',\n linkName: 'searchbox-_-text-_-autosuggest',\n },\n natural: {\n searchTermType: 'natural',\n linkName: 'searchbox-_-text-_-searchbutton',\n },\n};\n\n/**\n * Determines the search type based on the aria-activedescendant value.\n *\n * @param {string} [ariaAD] - The aria-activedescendant value\n * from the search field.\n * @returns {'searchHistory' | 'autosuggest' | 'natural'} - the type of\n * search sumbission, listbox selection or text entry (natural)\n */\nconst getSearchType = (ariaAD) => {\n if (ariaAD) {\n if (ariaAD.startsWith('search-history')) return 'searchHistory';\n if (ariaAD.startsWith('autosuggest')) return 'autosuggest';\n }\n return 'natural';\n};\n\n/**\n * Extracts the index number from an aria-activedescendant value.\n *\n * @param {string} [ariaAD=''] - The aria-activedescendant attribute value.\n * @returns {string | null} - The extracted index if found, otherwise null.\n */\nconst getTermIndex = (ariaAD = '') => {\n const match = ariaAD.match(/-([^-]*)$/);\n const lastString = match?.[1];\n const isNumber = lastString && !Number.isNaN(Number(lastString));\n return isNumber ? lastString : null;\n};\n\n/**\n * Retrieves the appropriate analytics object for a given search type.\n *\n * @param {string} ariaAD - The aria-activedescendant value.\n * @param {'searchHistory' | 'autosuggest' | 'natural'} searchType\n * The detected search type (see getSearchType)\n * @param {object} [condAnalytics=conditionalAnalytics] - additional analytics\n * object, specific to the search type (see conditionalAnalytics above)\n * @returns {object} - The resolved analytics object, with position added.\n */\nconst getConditionalAnalytics = (\n ariaAD,\n searchType,\n condAnalytics = conditionalAnalytics\n) => {\n const analytics = condAnalytics[searchType];\n if (searchType !== 'natural' && analytics) {\n // this is the position indicator regardless of which listbox\n analytics.autoSuggestPosition = getTermIndex(ariaAD);\n }\n return analytics || {};\n};\n\n/**\n * The finalized analtyics object\n *\n * @param {string} ariaAD - The aria-activedescendant value.\n * @param {string} searchTerm - The submitted search term.\n * @returns {object} - The complete analytics data for submission.\n */\nconst getSumbitAnalytics = (ariaAD, searchTerm) => {\n if (!searchTerm) return null;\n const searchType = getSearchType(ariaAD);\n\n return {\n events: 'event1',\n searchTermClicked: searchTerm,\n ...getConditionalAnalytics(ariaAD, searchType),\n };\n};\n\n/**\n * gets finalized analytics and submits them\n * also includes convenience logging for env without metrics available\n *\n * @param {string} ariaAD - The aria-activedescendant value.\n * @param {string} searchTerm - The submitted search term.\n */\nconst handleSearchFormSubmissionAnalytics = (ariaAD, searchTerm) => {\n const config = getSumbitAnalytics(ariaAD, searchTerm);\n if (!config) return;\n\n // help with reviewing analytics locally or on try server\n if (document?.location?.hostname !== 'www.rei.com') {\n /* eslint-disable no-console */\n console.group('Metrics link config - searchMetrics');\n Object.entries(config).forEach(([key, value]) => {\n console.log(`${key}: ${value}`);\n });\n console.groupEnd();\n /* eslint-enable no-console */\n }\n\n analyticsCustomClickHandler(config);\n};\n\nexport {\n getSearchType,\n getTermIndex,\n getConditionalAnalytics,\n getSumbitAnalytics,\n handleSearchFormSubmissionAnalytics,\n};\n","/**\n * Search Form, input field, clear button, and search button\n *\n * Separation of concerns is very important here, as the JS and templates\n * here have to work alongside external components (namely, autosuggest,\n * and eventually search history).\n *\n * Code here should only apply to the elements above(e.g. the basic search\n * form). Nothing that applies to a supplemental search form package should\n * be included here (for example: adjusting Search History state)\n *\n * This file is also the sole source of handling analytics events on\n * form submission. Outside components should send a 'termSelected' custom\n * event rather than calling submit() directly on the search form.\n */\nimport { getDomElems } from '../../../../_shared-components/getDomElems';\nimport { handleSearchFormSubmissionAnalytics } from './searchMetrics';\n\n/**\n * Retrieves a search query parameter from the current URL.\n *\n * @param {string} param - The name of the query parameter to retrieve.\n * @returns {string|null} The value of the query parameter, or null if not found.\n */\nconst getSearchParam = (param) => {\n const params = new URLSearchParams(window.location.search);\n return params.get(param);\n};\n\n/**\n * Displays the clear button and adjusts the search field's margin.\n *\n * @param {HTMLInputElement} searchField - The search input field element.\n * @param {HTMLElement} clearButton - The clear button element.\n */\nconst showClearButton = (searchField, clearButton) => {\n clearButton.removeAttribute('aria-hidden');\n clearButton.setAttribute('data-visible', 'visible');\n searchField.setAttribute('style', 'margin-right: -44px;');\n};\n\n/**\n * Hides the clear button and resets the search field's margin.\n *\n * @param {HTMLInputElement} searchField - The search input field element.\n * @param {HTMLElement} clearButton - The clear button element.\n */\nconst hideClearButton = (searchField, clearButton) => {\n clearButton.setAttribute('aria-hidden', 'true');\n clearButton.setAttribute('data-visible', 'hidden');\n searchField.setAttribute('style', 'margin-right: 0;');\n};\n\n/**\n * Toggles the visibility of the clear button based on the search field's value.\n *\n * @param {HTMLInputElement} searchField - The search input field element.\n * @param {HTMLElement} clearButton - The clear button element.\n */\nconst setClearButtonVisibility = (searchField, clearButton) => {\n if (!searchField.value) {\n hideClearButton(searchField, clearButton);\n } else {\n showClearButton(searchField, clearButton);\n }\n};\n\n/**\n * Enables or disables the search button based on the search field's value.\n *\n * @param {HTMLInputElement} searchField - The search input field element.\n * @param {HTMLButtonElement} searchButton - The search button element.\n */\nconst enableSearchButton = (searchField, searchButton) => {\n if (searchField.value.trim().length === 0) {\n searchButton.classList.add('search__search-button--disabled');\n searchButton.setAttribute('aria-disabled', 'true');\n searchButton.setAttribute('disabled', 'disabled');\n } else if (searchField.value.trim().length > 0) {\n searchButton.classList.remove('search__search-button--disabled');\n searchButton.setAttribute('aria-disabled', 'false');\n searchButton.removeAttribute('disabled');\n }\n};\n\n/**\n * Clears the search field's value and refocuses it.\n *\n * @param {HTMLInputElement} searchField - The search input field element.\n */\nconst clearSearchField = (searchField) => {\n const inputElem = searchField;\n inputElem.value = '';\n searchField.focus();\n};\n\n// used to ensure analytics don't send twice\nlet analyticsLogged = false;\n\n/**\n * handle sending analytics\n * triggered on either natural search (term in box, enter/searchbutton)\n * Or when \"termSelected\" event is dispatched by autosuggest or recents\n *\n * @param {HTMLInputElement} searchField - The search input field element.\n */\nconst handleAnalytics = (searchField) => {\n if (analyticsLogged) return;\n const searchTerm = searchField.value;\n const ariaActivedescendant = searchField.getAttribute('aria-activedescendant');\n handleSearchFormSubmissionAnalytics(ariaActivedescendant, searchTerm);\n analyticsLogged = true;\n};\n\n/**\n * Handles the \"termSelected\" event, triggering analytics tracking,\n * and submitting the search form.\n *\n * @param {HTMLInputElement} searchField - The search input field element.\n * @param {HTMLFormElement} searchForm - The search form element.\n * @param {Event} e\n */\nconst handleTermSelected = (searchField, searchForm, e) => {\n e.preventDefault();\n handleAnalytics(searchField);\n searchForm.submit();\n};\n\n/**\n * Initializes the search form event listeners and sets up analytics tracking.\n *\n * @param {Object} selectors - The object containing CSS selectors for various form elements.\n * @param {string} selectors.searchForm - Selector for the search form element.\n * @param {string} selectors.searchField - Selector for the search input field.\n * @param {string} selectors.clearButton - Selector for the search clear button.\n * @param {string} selectors.searchButton - Selector for the search submit button.\n */\nconst init = (selectors) => {\n // check that all needed elements are available\n const searchElems = getDomElems(selectors);\n if (searchElems === null) return;\n\n analyticsLogged = false;\n\n const {\n searchForm,\n searchField,\n clearButton,\n searchButton,\n } = searchElems;\n\n // if we're on a search page with a query, populate the search field with it\n searchField.value = getSearchParam('q');\n // set initial clear button visibility based on above\n setClearButtonVisibility(searchField, clearButton);\n // enable or disable search button based on above\n enableSearchButton(searchField, searchButton);\n\n // Clear button event listeners\n clearButton.addEventListener('click', () => {\n clearSearchField(searchField);\n setClearButtonVisibility(searchField, clearButton);\n enableSearchButton(searchField, searchButton);\n });\n searchField.addEventListener('keyup', () => {\n setClearButtonVisibility(searchField, clearButton);\n });\n\n // search button event listener\n searchField.addEventListener('input', () => {\n enableSearchButton(searchField, searchButton);\n });\n\n // term submission/analytics event listeners\n document.addEventListener('termSelected', (e) => {\n handleTermSelected(searchField, searchForm, e);\n });\n searchForm.addEventListener('submit', (e) => {\n handleAnalytics(searchField, searchForm, e);\n });\n};\n\nexport default init;\n","const sliceSize = 10;\n\nexport const cleanSearchResult = (item) => item.replace(/\\s?-?'?/gi, '');\n\nexport const getStartsWithData = (data, cleanedSearchTerm) => data\n .filter((item) => cleanSearchResult(item).startsWith(cleanedSearchTerm));\n\nexport const getIncludesData = (data, cleanedSearchTerm) => data\n .filter((item) => !cleanSearchResult(item).startsWith(cleanedSearchTerm)\n && cleanSearchResult(item).includes(cleanedSearchTerm));\n\nexport const partitionData = (data, cleanSearchTerm) => data.map((term) => {\n // Build a regular expression pattern with optional special characters\n // in-between each letter of the search term\n let optionalSpecialCharacterPattern = '';\n for (let i = 0, len = cleanSearchTerm.length; i < len; i += 1) {\n optionalSpecialCharacterPattern += `${cleanSearchTerm[i]}\\\\W*`;\n }\n\n // Create the regular expression, ignoring case and matching only the first occurrence\n const optionalSpecialCharacterRegExp = new RegExp(optionalSpecialCharacterPattern, 'i');\n\n // Match the search term with optional special characters to the item in the term list\n const matches = term.match(optionalSpecialCharacterRegExp);\n\n // Define the parameters that allow search term highlighting in the term\n // list, note there should only ever be one occurrence\n const displayTerm = matches ? matches[0] : cleanSearchTerm;\n\n return {\n searchTerm: displayTerm,\n resultTitle: term,\n };\n});\n\nexport const cleanSearchTerm = (term) => {\n // Regular expression to match all non-word (special) characters\n const specialCharacterRegExp = term.length > 1 ? /\\W/g : '';\n\n return term.toLowerCase().replace(/[^a-z\\d]/gi, '').replace(specialCharacterRegExp, '');\n};\n\nexport const cleanFetchedItems = (fetchedItems) => [...fetchedItems]\n .map((item) => item.term.toLowerCase());\n\n/** @param {function} filterResults takes @param {array} fetchedItems and filters it by using\n * the current value from the input box, as well as multiple steps of regex that return\n * matched string characters to allow for dynamic styling\n */\nexport default (value = '', state = {}) => {\n const newState = state;\n /** @param {function} filterResults takes @param {array} fetchedItems and filters it by using\n * the current value from the input box, as well as multiple steps of regex that return\n * matched string characters to allow for dynamic styling\n */\n const cleanedSearchTerm = cleanSearchTerm(value);\n\n if (!newState.fetchedItems.length) return [];\n // Grab all the values from fetchedItems and map over them to produce a\n // new array that has them lowercased and with only their term\n let data = cleanFetchedItems(newState.fetchedItems);\n\n // Grabs data that starts with search term\n const startsWithData = getStartsWithData(data, cleanedSearchTerm);\n\n // Grabs data that contains, but doesn't start with search term\n const includesData = getIncludesData(data, cleanedSearchTerm);\n\n // Combines data sets, if needed\n data = (startsWithData.length < sliceSize) ? startsWithData.concat(includesData) : startsWithData;\n\n // partition search results into the entered chars and remaining chars\n data = partitionData(data, cleanedSearchTerm);\n\n newState.filteredResults = data.slice(0, sliceSize);\n return newState.filteredResults;\n};\n","const SEARCH_HISTORY = {\n LI_ID: (index) => `search-history-entry-${index}`,\n UL_ID: 'gnav-recent-search-list',\n LOCAL_STORAGE_KEY: 'savedSearches',\n SELECTOR: '[data-js=recent-search-box]',\n RECENTLY_SELECTED: 'recentSearchTermSelected',\n DELETE: {\n ID: 'search-history-delete',\n CSS: 'search-history__button',\n TEXT: 'Clear History',\n },\n TITLE: {\n CSS: 'search-history__title',\n TEXT: 'Search History',\n },\n};\n\nconst AUTOSUGGEST = {\n LI_ID: (index) => `autosuggest-result-${index}`,\n UL_ID: 'gnav-suggestion-list',\n SELECTOR: '[data-js=suggestion-box]',\n URL: {\n FALLBACK: 'https://www.rei.com/autosuggest/online/details',\n },\n};\n\nconst LISTBOX = {\n CSS: {\n VISUAL_FOCUS: 'searchbox-term',\n LIST_ITEM: 'searchbox-result',\n },\n ARIA_LIVE: {\n DIRECTIONS: 'Keyboard users, use up and down arrows to review and enter to select. Touch device users, explore by touch or with swipe gestures.',\n SELECTOR: '[data-js=\"feedback-holder\"]',\n },\n ALL_LI: {\n SELECTOR: `[id^=${AUTOSUGGEST.LI_ID('')}], [id^=${SEARCH_HISTORY.LI_ID('')}]`,\n },\n URL: {\n DOMAIN: 'https://www.rei.com/',\n },\n};\n\nexport {\n AUTOSUGGEST,\n SEARCH_HISTORY,\n};\nexport default LISTBOX;\n","/**\n * Listbox Utils | Search History & Autosuggest\n * Search History and Autosuggest are very similar in functionality, but must be able to work\n * independently of one another. This module is for methods that are used in both recents and\n * autosuggest listboxes.\n * @see [Definition of listbox]{@link https://www.w3.org/TR/wai-aria-practices/#Listbox}.\n * Used in reference to [combo box]{@link https://www.w3.org/TR/wai-aria-practices/#combobox}\n */\nimport { code, getKeyCode, keyCode } from '../../../../_shared-components/keyCode';\nimport LISTBOX from './constants';\n\n/**\n * Keys whose default action should be ignored on a keydown event.\n * @param {KeyboardEvent} e\n */\nconst preventDefaultOnKeyDown = (e) => {\n const keyCodeVal = getKeyCode(e);\n switch (keyCodeVal) {\n case code.ARROW_DOWN:\n case keyCode.ARROW_DOWN:\n case code.ARROW_UP:\n case keyCode.ARROW_UP:\n case code.END:\n case keyCode.END:\n e.preventDefault();\n break;\n default:\n break;\n }\n};\n\nconst getUpdatedIndexOnArrowDown = (currentIndex, length) => (\n (currentIndex < length - 1 && currentIndex > -1) ? currentIndex + 1 : 0\n);\n\nconst getUpdatedIndexOnArrowUp = (currentIndex, length) => (\n (currentIndex <= length && currentIndex > 0) ? currentIndex - 1 : length - 1\n);\n\n/**\n * Sets the input field with the value.\n * @param {HTMLInputElement} searchField\n * @param {string} value\n */\nconst setSearchField = (searchField, value = '') => {\n const search = searchField;\n search.value = value;\n};\n\n/**\n * Iterates through list entries and removes css indicating visual focus.\n * @param {HTMLLIElement[]} listEntries\n */\nconst cleanListEntries = (listEntries) => {\n [...listEntries].forEach((elem) => {\n elem.classList.remove(LISTBOX.CSS.VISUAL_FOCUS);\n elem.removeAttribute('aria-selected');\n });\n};\n\nconst resetListbox = (searchField, listEntries) => {\n searchField.setAttribute('aria-activedescendant', '');\n cleanListEntries(listEntries);\n};\n\n/**\n * Index for the list item element is the last substring of the id when split on '-'.\n * @param {HTMLLIElement} elem\n * @returns {number|number}\n */\nconst getIndexFromElement = (elem) => {\n const { id = null } = elem || {};\n const wordsArr = !id ? [] : id.split('-');\n return wordsArr.length < 1 ? -1 : (wordsArr[wordsArr.length - 1] * 1);\n};\n\n/**\n * Adds visual focus to a single listItem and updates aria-live region with descriptive context.\n * @param {HTMLInputElement} searchField\n * @param {HTMLLIElement} listItem - entry to add visual focus to\n * @param {HTMLLIElement[]} listEntries - button element only for clear history\n */\nconst setVisualFocus = (searchField, listItem, listEntries) => {\n cleanListEntries(listEntries);\n searchField.setAttribute('aria-activedescendant', listItem.id);\n listItem.setAttribute('aria-selected', 'true');\n listItem.classList.add(LISTBOX.CSS.VISUAL_FOCUS);\n};\n\n/**\n * @param {HTMLDivElement} listboxElem - which contains the data-visible attribute which may\n * need to be updated\n * @param {boolean} newValue - most recent result of logic determining if listbox should visible\n * (true) or hidden (false)\n * @returns {boolean} - true if a difference exists between the previous and newValue\n */\nconst shouldUpdateVisibility = (listboxElem, newValue) => {\n const { dataset: { visible: visibleValue } } = listboxElem;\n const oldValue = visibleValue === 'visible';\n return oldValue !== newValue;\n};\n\nconst addMouseMoveEventListeners = (dynamicElem, enterCallback, leaveCallback) => {\n dynamicElem.addEventListener('mouseenter', enterCallback);\n dynamicElem.addEventListener('mouseleave', leaveCallback);\n};\n\n/**\n * A custom event for when a search term is selected.\n * Used to indicate to search.js to handle the search input submit event\n * and send analytics. This keeps the submission/analytics concern\n * housed ONLY in search.js\n *\n * @constant {CustomEvent} termSelectedEvent\n * @event termSelected\n */\nconst termSelectedEvent = new CustomEvent(\n 'termSelected',\n {\n bubbles: true,\n cancelable: true,\n },\n);\n\n/**\n * Retrieves the `id` and `textContent` of a list item (`