{"version":3,"file":"instantsearch.production.min.js","sources":["../node_modules/algoliasearch-helper/src/functions/merge.js","../node_modules/algoliasearch-helper/src/functions/defaultsPure.js","../node_modules/algoliasearch-helper/src/functions/intersection.js","../node_modules/algoliasearch-helper/src/functions/find.js","../node_modules/algoliasearch-helper/src/functions/valToNumber.js","../node_modules/algoliasearch-helper/src/functions/omit.js","../node_modules/algoliasearch-helper/src/functions/objectHasKeys.js","../node_modules/algoliasearch-helper/src/utils/isValidUserToken.js","../node_modules/algoliasearch-helper/src/SearchParameters/RefinementList.js","../node_modules/algoliasearch-helper/src/SearchParameters/index.js","../node_modules/algoliasearch-helper/src/functions/orderBy.js","../node_modules/algoliasearch-helper/src/functions/compact.js","../node_modules/algoliasearch-helper/src/functions/findIndex.js","../node_modules/algoliasearch-helper/src/functions/formatSort.js","../node_modules/algoliasearch-helper/src/SearchResults/generate-hierarchical-tree.js","../node_modules/algoliasearch-helper/src/SearchResults/index.js","../node_modules/events/events.js","../node_modules/algoliasearch-helper/src/functions/inherits.js","../node_modules/algoliasearch-helper/src/DerivedHelper/index.js","../node_modules/algoliasearch-helper/src/requestBuilder.js","../node_modules/algoliasearch-helper/src/version.js","../node_modules/algoliasearch-helper/src/algoliasearch.helper.js","../node_modules/algoliasearch-helper/index.js","../src/lib/utils/defer.ts","../src/lib/utils/getContainerNode.ts","../src/lib/utils/isDomElement.ts","../src/lib/utils/isSpecialClick.ts","../src/lib/utils/uniq.ts","../src/lib/utils/prepareTemplateProps.ts","../node_modules/hogan.js/lib/compiler.js","../node_modules/hogan.js/lib/template.js","../node_modules/hogan.js/lib/hogan.js","../src/lib/utils/renderTemplate.js","../src/lib/utils/find.ts","../src/lib/utils/unescapeRefinement.ts","../src/lib/utils/getRefinements.ts","../src/lib/utils/clearRefinements.ts","../src/lib/utils/escapeRefinement.ts","../src/lib/utils/checkRendering.ts","../src/lib/utils/getObjectType.ts","../src/lib/utils/noop.ts","../src/lib/utils/getPropertyByPath.ts","../src/lib/utils/isFiniteNumber.ts","../src/lib/utils/isPlainObject.ts","../src/lib/utils/range.ts","../src/lib/utils/isEqual.ts","../src/lib/utils/escape.ts","../src/lib/utils/unescape.ts","../src/lib/utils/escape-highlight.ts","../src/lib/utils/concatHighlightedParts.ts","../src/lib/utils/getHighlightedParts.ts","../src/lib/utils/getHighlightFromSiblings.ts","../src/lib/utils/reverseHighlightedParts.ts","../src/lib/utils/mergeSearchParameters.ts","../src/lib/utils/findIndex.ts","../src/lib/utils/toArray.ts","../src/lib/utils/documentation.ts","../src/lib/utils/geo-search.ts","../src/lib/utils/hits-absolute-position.ts","../src/lib/utils/hits-query-id.ts","../src/lib/utils/createSendEventForFacet.ts","../src/lib/utils/isFacetRefined.ts","../src/lib/utils/createSendEventForHits.ts","../src/lib/utils/convertNumericRefinementsToFilters.ts","../src/widgets/index/index.ts","../src/lib/utils/resolveSearchParameters.ts","../src/lib/suit.ts","../src/helpers/highlight.ts","../src/helpers/reverseHighlight.ts","../src/helpers/snippet.ts","../src/helpers/reverseSnippet.ts","../src/helpers/insights.ts","../src/helpers/get-insights-anonymous-user-token.ts","../src/lib/stateMappings/simple.ts","../node_modules/qs/lib/utils.js","../node_modules/qs/lib/stringify.js","../node_modules/qs/lib/parse.js","../src/lib/routers/history.ts","../node_modules/qs/lib/formats.js","../node_modules/qs/lib/index.js","../src/middlewares/createRouterMiddleware.ts","../src/middlewares/createMetadataMiddleware.ts","../src/lib/InstantSearch.ts","../src/connectors/clear-refinements/connectClearRefinements.ts","../src/lib/version.ts","../src/lib/createHelpers.ts","../src/connectors/current-refinements/connectCurrentRefinements.ts","../src/connectors/hierarchical-menu/connectHierarchicalMenu.js","../src/connectors/hits/connectHits.ts","../src/lib/insights/client.ts","../node_modules/preact/dist/preact.module.js","../src/lib/insights/listener.tsx","../src/connectors/hits-per-page/connectHitsPerPage.ts","../src/connectors/hits/connectHitsWithInsights.ts","../src/connectors/infinite-hits/connectInfiniteHits.ts","../src/connectors/infinite-hits/connectInfiniteHitsWithInsights.ts","../src/connectors/menu/connectMenu.js","../src/connectors/numeric-menu/connectNumericMenu.ts","../src/connectors/pagination/connectPagination.ts","../src/connectors/pagination/Paginator.ts","../src/connectors/range/connectRange.ts","../src/connectors/refinement-list/connectRefinementList.js","../src/connectors/search-box/connectSearchBox.js","../src/connectors/sort-by/connectSortBy.js","../src/connectors/rating-menu/connectRatingMenu.js","../src/connectors/stats/connectStats.js","../src/connectors/toggle-refinement/connectToggleRefinement.js","../src/connectors/breadcrumb/connectBreadcrumb.ts","../src/connectors/geo-search/connectGeoSearch.js","../src/connectors/powered-by/connectPoweredBy.ts","../src/connectors/configure/connectConfigure.ts","../src/connectors/configure-related-items/connectConfigureRelatedItems.ts","../src/connectors/autocomplete/connectAutocomplete.ts","../src/connectors/query-rules/connectQueryRules.ts","../src/lib/voiceSearchHelper/index.ts","../src/connectors/voice-search/connectVoiceSearch.ts","../node_modules/classnames/index.js","../src/components/Template/Template.js","../src/components/ClearRefinements/ClearRefinements.js","../src/components/CurrentRefinements/CurrentRefinements.tsx","../src/lib/utils/capitalize.ts","../src/widgets/current-refinements/current-refinements.tsx","../src/components/GeoSearchControls/GeoSearchButton.js","../src/widgets/clear-refinements/defaultTemplates.ts","../src/widgets/clear-refinements/clear-refinements.tsx","../src/components/GeoSearchControls/GeoSearchToggle.js","../src/components/GeoSearchControls/GeoSearchControls.js","../src/widgets/geo-search/GeoSearchRenderer.js","../src/widgets/geo-search/defaultTemplates.js","../src/widgets/geo-search/geo-search.js","../src/components/RefinementList/RefinementListItem.js","../src/components/SearchBox/SearchBox.js","../src/components/RefinementList/RefinementList.js","../src/widgets/hierarchical-menu/defaultTemplates.js","../src/widgets/hierarchical-menu/hierarchical-menu.js","../src/components/Hits/Hits.js","../src/widgets/hits/defaultTemplates.ts","../src/widgets/hits/hits.tsx","../src/components/Selector/Selector.js","../src/widgets/hits-per-page/hits-per-page.tsx","../src/widgets/infinite-hits/defaultTemplates.ts","../src/widgets/infinite-hits/infinite-hits.tsx","../src/components/InfiniteHits/InfiniteHits.tsx","../src/widgets/menu/defaultTemplates.js","../src/widgets/menu/menu.js","../src/widgets/search-box/defaultTemplates.js","../src/widgets/refinement-list/defaultTemplates.js","../src/widgets/refinement-list/refinement-list.js","../src/widgets/numeric-menu/defaultTemplates.ts","../src/widgets/numeric-menu/numeric-menu.tsx","../src/components/Pagination/PaginationLink.js","../src/components/Pagination/Pagination.js","../src/widgets/pagination/pagination.js","../src/components/RangeInput/RangeInput.js","../src/widgets/range-input/range-input.js","../src/widgets/search-box/search-box.js","../src/components/Slider/Rheostat.js","../src/components/Slider/Pit.js","../src/components/Slider/Slider.js","../src/widgets/range-slider/range-slider.js","../src/widgets/sort-by/sort-by.js","../src/widgets/rating-menu/defaultTemplates.js","../src/widgets/rating-menu/rating-menu.js","../src/components/Stats/Stats.js","../src/widgets/stats/defaultTemplates.js","../src/widgets/stats/stats.js","../src/components/ToggleRefinement/ToggleRefinement.js","../src/widgets/toggle-refinement/defaultTemplates.js","../src/widgets/toggle-refinement/toggle-refinement.js","../src/components/Breadcrumb/Breadcrumb.tsx","../src/widgets/analytics/analytics.ts","../src/widgets/breadcrumb/defaultTemplates.ts","../src/widgets/breadcrumb/breadcrumb.tsx","../src/components/MenuSelect/MenuSelect.tsx","../src/widgets/menu-select/defaultTemplates.js","../src/widgets/menu-select/menu-select.js","../src/components/PoweredBy/PoweredBy.tsx","../src/widgets/powered-by/powered-by.js","../node_modules/preact/hooks/dist/hooks.module.js","../src/components/Panel/Panel.tsx","../src/components/VoiceSearch/VoiceSearch.tsx","../src/widgets/voice-search/voice-search.tsx","../src/components/QueryRuleCustomData/QueryRuleCustomData.tsx","../src/widgets/query-rule-custom-data/query-rule-custom-data.tsx","../src/widgets/panel/panel.tsx","../src/widgets/voice-search/defaultTemplates.js","../src/widgets/query-rule-context/query-rule-context.tsx","../src/widgets/configure/configure.ts","../src/widgets/configure-related-items/configure-related-items.ts","../src/widgets/geo-search/createHTMLMarker.ts","../src/widgets/places/places.ts","../src/middlewares/createInsightsMiddleware.ts","../src/lib/utils/getAppIdAndApiKey.ts","../src/lib/stateMappings/singleIndex.ts","../src/lib/infiniteHitsCache/sessionStorage.ts","../src/lib/main.ts"],"sourcesContent":["'use strict';\n\nfunction clone(value) {\n if (typeof value === 'object' && value !== null) {\n return _merge(Array.isArray(value) ? [] : {}, value);\n }\n return value;\n}\n\nfunction isObjectOrArrayOrFunction(value) {\n return (\n typeof value === 'function' ||\n Array.isArray(value) ||\n Object.prototype.toString.call(value) === '[object Object]'\n );\n}\n\nfunction _merge(target, source) {\n if (target === source) {\n return target;\n }\n\n for (var key in source) {\n if (!Object.prototype.hasOwnProperty.call(source, key)) {\n continue;\n }\n\n var sourceVal = source[key];\n var targetVal = target[key];\n\n if (typeof targetVal !== 'undefined' && typeof sourceVal === 'undefined') {\n continue;\n }\n\n if (isObjectOrArrayOrFunction(targetVal) && isObjectOrArrayOrFunction(sourceVal)) {\n target[key] = _merge(targetVal, sourceVal);\n } else {\n target[key] = clone(sourceVal);\n }\n }\n return target;\n}\n\n/**\n * This method is like Object.assign, but recursively merges own and inherited\n * enumerable keyed properties of source objects into the destination object.\n *\n * NOTE: this behaves like lodash/merge, but:\n * - does mutate functions if they are a source\n * - treats non-plain objects as plain\n * - does not work for circular objects\n * - treats sparse arrays as sparse\n * - does not convert Array-like objects (Arguments, NodeLists, etc.) to arrays\n *\n * @param {Object} object The destination object.\n * @param {...Object} [sources] The source objects.\n * @returns {Object} Returns `object`.\n */\n\nfunction merge(target) {\n if (!isObjectOrArrayOrFunction(target)) {\n target = {};\n }\n\n for (var i = 1, l = arguments.length; i < l; i++) {\n var source = arguments[i];\n\n if (isObjectOrArrayOrFunction(source)) {\n _merge(target, source);\n }\n }\n return target;\n}\n\nmodule.exports = merge;\n","'use strict';\n\n// NOTE: this behaves like lodash/defaults, but doesn't mutate the target\n// it also preserve keys order\nmodule.exports = function defaultsPure() {\n var sources = Array.prototype.slice.call(arguments);\n\n return sources.reduceRight(function(acc, source) {\n Object.keys(Object(source)).forEach(function(key) {\n if (source[key] === undefined) {\n return;\n }\n if (acc[key] !== undefined) {\n // remove if already added, so that we can add it in correct order\n delete acc[key];\n }\n acc[key] = source[key];\n });\n return acc;\n }, {});\n};\n","'use strict';\n\nfunction intersection(arr1, arr2) {\n return arr1.filter(function(value, index) {\n return (\n arr2.indexOf(value) > -1 &&\n arr1.indexOf(value) === index /* skips duplicates */\n );\n });\n}\n\nmodule.exports = intersection;\n","'use strict';\n\n// @MAJOR can be replaced by native Array#find when we change support\nmodule.exports = function find(array, comparator) {\n if (!Array.isArray(array)) {\n return undefined;\n }\n\n for (var i = 0; i < array.length; i++) {\n if (comparator(array[i])) {\n return array[i];\n }\n }\n};\n","'use strict';\n\nfunction valToNumber(v) {\n if (typeof v === 'number') {\n return v;\n } else if (typeof v === 'string') {\n return parseFloat(v);\n } else if (Array.isArray(v)) {\n return v.map(valToNumber);\n }\n\n throw new Error('The value should be a number, a parsable string or an array of those.');\n}\n\nmodule.exports = valToNumber;\n","'use strict';\n\n// https://github.com/babel/babel/blob/3aaafae053fa75febb3aa45d45b6f00646e30ba4/packages/babel-helpers/src/helpers.js#L604-L620\nfunction _objectWithoutPropertiesLoose(source, excluded) {\n if (source === null) return {};\n var target = {};\n var sourceKeys = Object.keys(source);\n var key;\n var i;\n for (i = 0; i < sourceKeys.length; i++) {\n key = sourceKeys[i];\n if (excluded.indexOf(key) >= 0) continue;\n target[key] = source[key];\n }\n return target;\n}\n\nmodule.exports = _objectWithoutPropertiesLoose;\n","'use strict';\n\nfunction objectHasKeys(obj) {\n return obj && Object.keys(obj).length > 0;\n}\n\nmodule.exports = objectHasKeys;\n","'use strict';\n\nmodule.exports = function isValidUserToken(userToken) {\n if (userToken === null) {\n return false;\n }\n return /^[a-zA-Z0-9_-]{1,64}$/.test(userToken);\n};\n","'use strict';\n\n/**\n * Functions to manipulate refinement lists\n *\n * The RefinementList is not formally defined through a prototype but is based\n * on a specific structure.\n *\n * @module SearchParameters.refinementList\n *\n * @typedef {string[]} SearchParameters.refinementList.Refinements\n * @typedef {Object.} SearchParameters.refinementList.RefinementList\n */\n\nvar defaultsPure = require('../functions/defaultsPure');\nvar omit = require('../functions/omit');\nvar objectHasKeys = require('../functions/objectHasKeys');\n\nvar lib = {\n /**\n * Adds a refinement to a RefinementList\n * @param {RefinementList} refinementList the initial list\n * @param {string} attribute the attribute to refine\n * @param {string} value the value of the refinement, if the value is not a string it will be converted\n * @return {RefinementList} a new and updated refinement list\n */\n addRefinement: function addRefinement(refinementList, attribute, value) {\n if (lib.isRefined(refinementList, attribute, value)) {\n return refinementList;\n }\n\n var valueAsString = '' + value;\n\n var facetRefinement = !refinementList[attribute] ?\n [valueAsString] :\n refinementList[attribute].concat(valueAsString);\n\n var mod = {};\n\n mod[attribute] = facetRefinement;\n\n return defaultsPure({}, mod, refinementList);\n },\n /**\n * Removes refinement(s) for an attribute:\n * - if the value is specified removes the refinement for the value on the attribute\n * - if no value is specified removes all the refinements for this attribute\n * @param {RefinementList} refinementList the initial list\n * @param {string} attribute the attribute to refine\n * @param {string} [value] the value of the refinement\n * @return {RefinementList} a new and updated refinement lst\n */\n removeRefinement: function removeRefinement(refinementList, attribute, value) {\n if (value === undefined) {\n // we use the \"filter\" form of clearRefinement, since it leaves empty values as-is\n // the form with a string will remove the attribute completely\n return lib.clearRefinement(refinementList, function(v, f) {\n return attribute === f;\n });\n }\n\n var valueAsString = '' + value;\n\n return lib.clearRefinement(refinementList, function(v, f) {\n return attribute === f && valueAsString === v;\n });\n },\n /**\n * Toggles the refinement value for an attribute.\n * @param {RefinementList} refinementList the initial list\n * @param {string} attribute the attribute to refine\n * @param {string} value the value of the refinement\n * @return {RefinementList} a new and updated list\n */\n toggleRefinement: function toggleRefinement(refinementList, attribute, value) {\n if (value === undefined) throw new Error('toggleRefinement should be used with a value');\n\n if (lib.isRefined(refinementList, attribute, value)) {\n return lib.removeRefinement(refinementList, attribute, value);\n }\n\n return lib.addRefinement(refinementList, attribute, value);\n },\n /**\n * Clear all or parts of a RefinementList. Depending on the arguments, three\n * kinds of behavior can happen:\n * - if no attribute is provided: clears the whole list\n * - if an attribute is provided as a string: clears the list for the specific attribute\n * - if an attribute is provided as a function: discards the elements for which the function returns true\n * @param {RefinementList} refinementList the initial list\n * @param {string} [attribute] the attribute or function to discard\n * @param {string} [refinementType] optional parameter to give more context to the attribute function\n * @return {RefinementList} a new and updated refinement list\n */\n clearRefinement: function clearRefinement(refinementList, attribute, refinementType) {\n if (attribute === undefined) {\n if (!objectHasKeys(refinementList)) {\n return refinementList;\n }\n return {};\n } else if (typeof attribute === 'string') {\n return omit(refinementList, [attribute]);\n } else if (typeof attribute === 'function') {\n var hasChanged = false;\n\n var newRefinementList = Object.keys(refinementList).reduce(function(memo, key) {\n var values = refinementList[key] || [];\n var facetList = values.filter(function(value) {\n return !attribute(value, key, refinementType);\n });\n\n if (facetList.length !== values.length) {\n hasChanged = true;\n }\n memo[key] = facetList;\n\n return memo;\n }, {});\n\n if (hasChanged) return newRefinementList;\n return refinementList;\n }\n },\n /**\n * Test if the refinement value is used for the attribute. If no refinement value\n * is provided, test if the refinementList contains any refinement for the\n * given attribute.\n * @param {RefinementList} refinementList the list of refinement\n * @param {string} attribute name of the attribute\n * @param {string} [refinementValue] value of the filter/refinement\n * @return {boolean}\n */\n isRefined: function isRefined(refinementList, attribute, refinementValue) {\n var containsRefinements = !!refinementList[attribute] &&\n refinementList[attribute].length > 0;\n\n if (refinementValue === undefined || !containsRefinements) {\n return containsRefinements;\n }\n\n var refinementValueAsString = '' + refinementValue;\n\n return refinementList[attribute].indexOf(refinementValueAsString) !== -1;\n }\n};\n\nmodule.exports = lib;\n","'use strict';\n\nvar merge = require('../functions/merge');\nvar defaultsPure = require('../functions/defaultsPure');\nvar intersection = require('../functions/intersection');\nvar find = require('../functions/find');\nvar valToNumber = require('../functions/valToNumber');\nvar omit = require('../functions/omit');\nvar objectHasKeys = require('../functions/objectHasKeys');\nvar isValidUserToken = require('../utils/isValidUserToken');\n\nvar RefinementList = require('./RefinementList');\n\n/**\n * isEqual, but only for numeric refinement values, possible values:\n * - 5\n * - [5]\n * - [[5]]\n * - [[5,5],[4]]\n */\nfunction isEqualNumericRefinement(a, b) {\n if (Array.isArray(a) && Array.isArray(b)) {\n return (\n a.length === b.length &&\n a.every(function(el, i) {\n return isEqualNumericRefinement(b[i], el);\n })\n );\n }\n return a === b;\n}\n\n/**\n * like _.find but using deep equality to be able to use it\n * to find arrays.\n * @private\n * @param {any[]} array array to search into (elements are base or array of base)\n * @param {any} searchedValue the value we're looking for (base or array of base)\n * @return {any} the searched value or undefined\n */\nfunction findArray(array, searchedValue) {\n return find(array, function(currentValue) {\n return isEqualNumericRefinement(currentValue, searchedValue);\n });\n}\n\n/**\n * The facet list is the structure used to store the list of values used to\n * filter a single attribute.\n * @typedef {string[]} SearchParameters.FacetList\n */\n\n/**\n * Structure to store numeric filters with the operator as the key. The supported operators\n * are `=`, `>`, `<`, `>=`, `<=` and `!=`.\n * @typedef {Object.>} SearchParameters.OperatorList\n */\n\n/**\n * SearchParameters is the data structure that contains all the information\n * usable for making a search to Algolia API. It doesn't do the search itself,\n * nor does it contains logic about the parameters.\n * It is an immutable object, therefore it has been created in a way that each\n * changes does not change the object itself but returns a copy with the\n * modification.\n * This object should probably not be instantiated outside of the helper. It will\n * be provided when needed. This object is documented for reference as you'll\n * get it from events generated by the {@link AlgoliaSearchHelper}.\n * If need be, instantiate the Helper from the factory function {@link SearchParameters.make}\n * @constructor\n * @classdesc contains all the parameters of a search\n * @param {object|SearchParameters} newParameters existing parameters or partial object\n * for the properties of a new SearchParameters\n * @see SearchParameters.make\n * @example SearchParameters of the first query in\n * the instant search demo\n{\n \"query\": \"\",\n \"disjunctiveFacets\": [\n \"customerReviewCount\",\n \"category\",\n \"salePrice_range\",\n \"manufacturer\"\n ],\n \"maxValuesPerFacet\": 30,\n \"page\": 0,\n \"hitsPerPage\": 10,\n \"facets\": [\n \"type\",\n \"shipping\"\n ]\n}\n */\nfunction SearchParameters(newParameters) {\n var params = newParameters ? SearchParameters._parseNumbers(newParameters) : {};\n\n if (params.userToken !== undefined && !isValidUserToken(params.userToken)) {\n console.warn('[algoliasearch-helper] The `userToken` parameter is invalid. This can lead to wrong analytics.\\n - Format: [a-zA-Z0-9_-]{1,64}');\n }\n /**\n * This attribute contains the list of all the conjunctive facets\n * used. This list will be added to requested facets in the\n * [facets attribute](https://www.algolia.com/doc/rest-api/search#param-facets) sent to algolia.\n * @member {string[]}\n */\n this.facets = params.facets || [];\n /**\n * This attribute contains the list of all the disjunctive facets\n * used. This list will be added to requested facets in the\n * [facets attribute](https://www.algolia.com/doc/rest-api/search#param-facets) sent to algolia.\n * @member {string[]}\n */\n this.disjunctiveFacets = params.disjunctiveFacets || [];\n /**\n * This attribute contains the list of all the hierarchical facets\n * used. This list will be added to requested facets in the\n * [facets attribute](https://www.algolia.com/doc/rest-api/search#param-facets) sent to algolia.\n * Hierarchical facets are a sub type of disjunctive facets that\n * let you filter faceted attributes hierarchically.\n * @member {string[]|object[]}\n */\n this.hierarchicalFacets = params.hierarchicalFacets || [];\n\n // Refinements\n /**\n * This attribute contains all the filters that need to be\n * applied on the conjunctive facets. Each facet must be properly\n * defined in the `facets` attribute.\n *\n * The key is the name of the facet, and the `FacetList` contains all\n * filters selected for the associated facet name.\n *\n * When querying algolia, the values stored in this attribute will\n * be translated into the `facetFilters` attribute.\n * @member {Object.}\n */\n this.facetsRefinements = params.facetsRefinements || {};\n /**\n * This attribute contains all the filters that need to be\n * excluded from the conjunctive facets. Each facet must be properly\n * defined in the `facets` attribute.\n *\n * The key is the name of the facet, and the `FacetList` contains all\n * filters excluded for the associated facet name.\n *\n * When querying algolia, the values stored in this attribute will\n * be translated into the `facetFilters` attribute.\n * @member {Object.}\n */\n this.facetsExcludes = params.facetsExcludes || {};\n /**\n * This attribute contains all the filters that need to be\n * applied on the disjunctive facets. Each facet must be properly\n * defined in the `disjunctiveFacets` attribute.\n *\n * The key is the name of the facet, and the `FacetList` contains all\n * filters selected for the associated facet name.\n *\n * When querying algolia, the values stored in this attribute will\n * be translated into the `facetFilters` attribute.\n * @member {Object.}\n */\n this.disjunctiveFacetsRefinements = params.disjunctiveFacetsRefinements || {};\n /**\n * This attribute contains all the filters that need to be\n * applied on the numeric attributes.\n *\n * The key is the name of the attribute, and the value is the\n * filters to apply to this attribute.\n *\n * When querying algolia, the values stored in this attribute will\n * be translated into the `numericFilters` attribute.\n * @member {Object.}\n */\n this.numericRefinements = params.numericRefinements || {};\n /**\n * This attribute contains all the tags used to refine the query.\n *\n * When querying algolia, the values stored in this attribute will\n * be translated into the `tagFilters` attribute.\n * @member {string[]}\n */\n this.tagRefinements = params.tagRefinements || [];\n /**\n * This attribute contains all the filters that need to be\n * applied on the hierarchical facets. Each facet must be properly\n * defined in the `hierarchicalFacets` attribute.\n *\n * The key is the name of the facet, and the `FacetList` contains all\n * filters selected for the associated facet name. The FacetList values\n * are structured as a string that contain the values for each level\n * separated by the configured separator.\n *\n * When querying algolia, the values stored in this attribute will\n * be translated into the `facetFilters` attribute.\n * @member {Object.}\n */\n this.hierarchicalFacetsRefinements = params.hierarchicalFacetsRefinements || {};\n\n var self = this;\n Object.keys(params).forEach(function(paramName) {\n var isKeyKnown = SearchParameters.PARAMETERS.indexOf(paramName) !== -1;\n var isValueDefined = params[paramName] !== undefined;\n\n if (!isKeyKnown && isValueDefined) {\n self[paramName] = params[paramName];\n }\n });\n}\n\n/**\n * List all the properties in SearchParameters and therefore all the known Algolia properties\n * This doesn't contain any beta/hidden features.\n * @private\n */\nSearchParameters.PARAMETERS = Object.keys(new SearchParameters());\n\n/**\n * @private\n * @param {object} partialState full or part of a state\n * @return {object} a new object with the number keys as number\n */\nSearchParameters._parseNumbers = function(partialState) {\n // Do not reparse numbers in SearchParameters, they ought to be parsed already\n if (partialState instanceof SearchParameters) return partialState;\n\n var numbers = {};\n\n var numberKeys = [\n 'aroundPrecision',\n 'aroundRadius',\n 'getRankingInfo',\n 'minWordSizefor2Typos',\n 'minWordSizefor1Typo',\n 'page',\n 'maxValuesPerFacet',\n 'distinct',\n 'minimumAroundRadius',\n 'hitsPerPage',\n 'minProximity'\n ];\n\n numberKeys.forEach(function(k) {\n var value = partialState[k];\n if (typeof value === 'string') {\n var parsedValue = parseFloat(value);\n // global isNaN is ok to use here, value is only number or NaN\n numbers[k] = isNaN(parsedValue) ? value : parsedValue;\n }\n });\n\n // there's two formats of insideBoundingBox, we need to parse\n // the one which is an array of float geo rectangles\n if (Array.isArray(partialState.insideBoundingBox)) {\n numbers.insideBoundingBox = partialState.insideBoundingBox.map(function(geoRect) {\n if (Array.isArray(geoRect)) {\n return geoRect.map(function(value) {\n return parseFloat(value);\n });\n }\n return geoRect;\n });\n }\n\n if (partialState.numericRefinements) {\n var numericRefinements = {};\n Object.keys(partialState.numericRefinements).forEach(function(attribute) {\n var operators = partialState.numericRefinements[attribute] || {};\n numericRefinements[attribute] = {};\n Object.keys(operators).forEach(function(operator) {\n var values = operators[operator];\n var parsedValues = values.map(function(v) {\n if (Array.isArray(v)) {\n return v.map(function(vPrime) {\n if (typeof vPrime === 'string') {\n return parseFloat(vPrime);\n }\n return vPrime;\n });\n } else if (typeof v === 'string') {\n return parseFloat(v);\n }\n return v;\n });\n numericRefinements[attribute][operator] = parsedValues;\n });\n });\n numbers.numericRefinements = numericRefinements;\n }\n\n return merge({}, partialState, numbers);\n};\n\n/**\n * Factory for SearchParameters\n * @param {object|SearchParameters} newParameters existing parameters or partial\n * object for the properties of a new SearchParameters\n * @return {SearchParameters} frozen instance of SearchParameters\n */\nSearchParameters.make = function makeSearchParameters(newParameters) {\n var instance = new SearchParameters(newParameters);\n\n var hierarchicalFacets = newParameters.hierarchicalFacets || [];\n hierarchicalFacets.forEach(function(facet) {\n if (facet.rootPath) {\n var currentRefinement = instance.getHierarchicalRefinement(facet.name);\n\n if (currentRefinement.length > 0 && currentRefinement[0].indexOf(facet.rootPath) !== 0) {\n instance = instance.clearRefinements(facet.name);\n }\n\n // get it again in case it has been cleared\n currentRefinement = instance.getHierarchicalRefinement(facet.name);\n if (currentRefinement.length === 0) {\n instance = instance.toggleHierarchicalFacetRefinement(facet.name, facet.rootPath);\n }\n }\n });\n\n return instance;\n};\n\n/**\n * Validates the new parameters based on the previous state\n * @param {SearchParameters} currentState the current state\n * @param {object|SearchParameters} parameters the new parameters to set\n * @return {Error|null} Error if the modification is invalid, null otherwise\n */\nSearchParameters.validate = function(currentState, parameters) {\n var params = parameters || {};\n\n if (currentState.tagFilters && params.tagRefinements && params.tagRefinements.length > 0) {\n return new Error(\n '[Tags] Cannot switch from the managed tag API to the advanced API. It is probably ' +\n 'an error, if it is really what you want, you should first clear the tags with clearTags method.');\n }\n\n if (currentState.tagRefinements.length > 0 && params.tagFilters) {\n return new Error(\n '[Tags] Cannot switch from the advanced tag API to the managed API. It is probably ' +\n 'an error, if it is not, you should first clear the tags with clearTags method.');\n }\n\n if (\n currentState.numericFilters &&\n params.numericRefinements &&\n objectHasKeys(params.numericRefinements)\n ) {\n return new Error(\n \"[Numeric filters] Can't switch from the advanced to the managed API. It\" +\n ' is probably an error, if this is really what you want, you have to first' +\n ' clear the numeric filters.'\n );\n }\n\n if (objectHasKeys(currentState.numericRefinements) && params.numericFilters) {\n return new Error(\n \"[Numeric filters] Can't switch from the managed API to the advanced. It\" +\n ' is probably an error, if this is really what you want, you have to first' +\n ' clear the numeric filters.');\n }\n\n return null;\n};\n\nSearchParameters.prototype = {\n constructor: SearchParameters,\n\n /**\n * Remove all refinements (disjunctive + conjunctive + excludes + numeric filters)\n * @method\n * @param {undefined|string|SearchParameters.clearCallback} [attribute] optional string or function\n * - If not given, means to clear all the filters.\n * - If `string`, means to clear all refinements for the `attribute` named filter.\n * - If `function`, means to clear all the refinements that return truthy values.\n * @return {SearchParameters}\n */\n clearRefinements: function clearRefinements(attribute) {\n var patch = {\n numericRefinements: this._clearNumericRefinements(attribute),\n facetsRefinements: RefinementList.clearRefinement(\n this.facetsRefinements,\n attribute,\n 'conjunctiveFacet'\n ),\n facetsExcludes: RefinementList.clearRefinement(\n this.facetsExcludes,\n attribute,\n 'exclude'\n ),\n disjunctiveFacetsRefinements: RefinementList.clearRefinement(\n this.disjunctiveFacetsRefinements,\n attribute,\n 'disjunctiveFacet'\n ),\n hierarchicalFacetsRefinements: RefinementList.clearRefinement(\n this.hierarchicalFacetsRefinements,\n attribute,\n 'hierarchicalFacet'\n )\n };\n if (\n patch.numericRefinements === this.numericRefinements &&\n patch.facetsRefinements === this.facetsRefinements &&\n patch.facetsExcludes === this.facetsExcludes &&\n patch.disjunctiveFacetsRefinements === this.disjunctiveFacetsRefinements &&\n patch.hierarchicalFacetsRefinements === this.hierarchicalFacetsRefinements\n ) {\n return this;\n }\n return this.setQueryParameters(patch);\n },\n /**\n * Remove all the refined tags from the SearchParameters\n * @method\n * @return {SearchParameters}\n */\n clearTags: function clearTags() {\n if (this.tagFilters === undefined && this.tagRefinements.length === 0) return this;\n\n return this.setQueryParameters({\n tagFilters: undefined,\n tagRefinements: []\n });\n },\n /**\n * Set the index.\n * @method\n * @param {string} index the index name\n * @return {SearchParameters}\n */\n setIndex: function setIndex(index) {\n if (index === this.index) return this;\n\n return this.setQueryParameters({\n index: index\n });\n },\n /**\n * Query setter\n * @method\n * @param {string} newQuery value for the new query\n * @return {SearchParameters}\n */\n setQuery: function setQuery(newQuery) {\n if (newQuery === this.query) return this;\n\n return this.setQueryParameters({\n query: newQuery\n });\n },\n /**\n * Page setter\n * @method\n * @param {number} newPage new page number\n * @return {SearchParameters}\n */\n setPage: function setPage(newPage) {\n if (newPage === this.page) return this;\n\n return this.setQueryParameters({\n page: newPage\n });\n },\n /**\n * Facets setter\n * The facets are the simple facets, used for conjunctive (and) faceting.\n * @method\n * @param {string[]} facets all the attributes of the algolia records used for conjunctive faceting\n * @return {SearchParameters}\n */\n setFacets: function setFacets(facets) {\n return this.setQueryParameters({\n facets: facets\n });\n },\n /**\n * Disjunctive facets setter\n * Change the list of disjunctive (or) facets the helper chan handle.\n * @method\n * @param {string[]} facets all the attributes of the algolia records used for disjunctive faceting\n * @return {SearchParameters}\n */\n setDisjunctiveFacets: function setDisjunctiveFacets(facets) {\n return this.setQueryParameters({\n disjunctiveFacets: facets\n });\n },\n /**\n * HitsPerPage setter\n * Hits per page represents the number of hits retrieved for this query\n * @method\n * @param {number} n number of hits retrieved per page of results\n * @return {SearchParameters}\n */\n setHitsPerPage: function setHitsPerPage(n) {\n if (this.hitsPerPage === n) return this;\n\n return this.setQueryParameters({\n hitsPerPage: n\n });\n },\n /**\n * typoTolerance setter\n * Set the value of typoTolerance\n * @method\n * @param {string} typoTolerance new value of typoTolerance (\"true\", \"false\", \"min\" or \"strict\")\n * @return {SearchParameters}\n */\n setTypoTolerance: function setTypoTolerance(typoTolerance) {\n if (this.typoTolerance === typoTolerance) return this;\n\n return this.setQueryParameters({\n typoTolerance: typoTolerance\n });\n },\n /**\n * Add a numeric filter for a given attribute\n * When value is an array, they are combined with OR\n * When value is a single value, it will combined with AND\n * @method\n * @param {string} attribute attribute to set the filter on\n * @param {string} operator operator of the filter (possible values: =, >, >=, <, <=, !=)\n * @param {number | number[]} value value of the filter\n * @return {SearchParameters}\n * @example\n * // for price = 50 or 40\n * searchparameter.addNumericRefinement('price', '=', [50, 40]);\n * @example\n * // for size = 38 and 40\n * searchparameter.addNumericRefinement('size', '=', 38);\n * searchparameter.addNumericRefinement('size', '=', 40);\n */\n addNumericRefinement: function(attribute, operator, v) {\n var value = valToNumber(v);\n\n if (this.isNumericRefined(attribute, operator, value)) return this;\n\n var mod = merge({}, this.numericRefinements);\n\n mod[attribute] = merge({}, mod[attribute]);\n\n if (mod[attribute][operator]) {\n // Array copy\n mod[attribute][operator] = mod[attribute][operator].slice();\n // Add the element. Concat can't be used here because value can be an array.\n mod[attribute][operator].push(value);\n } else {\n mod[attribute][operator] = [value];\n }\n\n return this.setQueryParameters({\n numericRefinements: mod\n });\n },\n /**\n * Get the list of conjunctive refinements for a single facet\n * @param {string} facetName name of the attribute used for faceting\n * @return {string[]} list of refinements\n */\n getConjunctiveRefinements: function(facetName) {\n if (!this.isConjunctiveFacet(facetName)) {\n return [];\n }\n return this.facetsRefinements[facetName] || [];\n },\n /**\n * Get the list of disjunctive refinements for a single facet\n * @param {string} facetName name of the attribute used for faceting\n * @return {string[]} list of refinements\n */\n getDisjunctiveRefinements: function(facetName) {\n if (!this.isDisjunctiveFacet(facetName)) {\n return [];\n }\n return this.disjunctiveFacetsRefinements[facetName] || [];\n },\n /**\n * Get the list of hierarchical refinements for a single facet\n * @param {string} facetName name of the attribute used for faceting\n * @return {string[]} list of refinements\n */\n getHierarchicalRefinement: function(facetName) {\n // we send an array but we currently do not support multiple\n // hierarchicalRefinements for a hierarchicalFacet\n return this.hierarchicalFacetsRefinements[facetName] || [];\n },\n /**\n * Get the list of exclude refinements for a single facet\n * @param {string} facetName name of the attribute used for faceting\n * @return {string[]} list of refinements\n */\n getExcludeRefinements: function(facetName) {\n if (!this.isConjunctiveFacet(facetName)) {\n return [];\n }\n return this.facetsExcludes[facetName] || [];\n },\n\n /**\n * Remove all the numeric filter for a given (attribute, operator)\n * @method\n * @param {string} attribute attribute to set the filter on\n * @param {string} [operator] operator of the filter (possible values: =, >, >=, <, <=, !=)\n * @param {number} [number] the value to be removed\n * @return {SearchParameters}\n */\n removeNumericRefinement: function(attribute, operator, paramValue) {\n if (paramValue !== undefined) {\n if (!this.isNumericRefined(attribute, operator, paramValue)) {\n return this;\n }\n return this.setQueryParameters({\n numericRefinements: this._clearNumericRefinements(function(value, key) {\n return (\n key === attribute &&\n value.op === operator &&\n isEqualNumericRefinement(value.val, valToNumber(paramValue))\n );\n })\n });\n } else if (operator !== undefined) {\n if (!this.isNumericRefined(attribute, operator)) return this;\n return this.setQueryParameters({\n numericRefinements: this._clearNumericRefinements(function(value, key) {\n return key === attribute && value.op === operator;\n })\n });\n }\n\n if (!this.isNumericRefined(attribute)) return this;\n return this.setQueryParameters({\n numericRefinements: this._clearNumericRefinements(function(value, key) {\n return key === attribute;\n })\n });\n },\n /**\n * Get the list of numeric refinements for a single facet\n * @param {string} facetName name of the attribute used for faceting\n * @return {SearchParameters.OperatorList} list of refinements\n */\n getNumericRefinements: function(facetName) {\n return this.numericRefinements[facetName] || {};\n },\n /**\n * Return the current refinement for the (attribute, operator)\n * @param {string} attribute attribute in the record\n * @param {string} operator operator applied on the refined values\n * @return {Array.} refined values\n */\n getNumericRefinement: function(attribute, operator) {\n return this.numericRefinements[attribute] && this.numericRefinements[attribute][operator];\n },\n /**\n * Clear numeric filters.\n * @method\n * @private\n * @param {string|SearchParameters.clearCallback} [attribute] optional string or function\n * - If not given, means to clear all the filters.\n * - If `string`, means to clear all refinements for the `attribute` named filter.\n * - If `function`, means to clear all the refinements that return truthy values.\n * @return {Object.}\n */\n _clearNumericRefinements: function _clearNumericRefinements(attribute) {\n if (attribute === undefined) {\n if (!objectHasKeys(this.numericRefinements)) {\n return this.numericRefinements;\n }\n return {};\n } else if (typeof attribute === 'string') {\n return omit(this.numericRefinements, [attribute]);\n } else if (typeof attribute === 'function') {\n var hasChanged = false;\n var numericRefinements = this.numericRefinements;\n var newNumericRefinements = Object.keys(numericRefinements).reduce(function(memo, key) {\n var operators = numericRefinements[key];\n var operatorList = {};\n\n operators = operators || {};\n Object.keys(operators).forEach(function(operator) {\n var values = operators[operator] || [];\n var outValues = [];\n values.forEach(function(value) {\n var predicateResult = attribute({val: value, op: operator}, key, 'numeric');\n if (!predicateResult) outValues.push(value);\n });\n if (outValues.length !== values.length) {\n hasChanged = true;\n }\n operatorList[operator] = outValues;\n });\n\n memo[key] = operatorList;\n\n return memo;\n }, {});\n\n if (hasChanged) return newNumericRefinements;\n return this.numericRefinements;\n }\n },\n /**\n * Add a facet to the facets attribute of the helper configuration, if it\n * isn't already present.\n * @method\n * @param {string} facet facet name to add\n * @return {SearchParameters}\n */\n addFacet: function addFacet(facet) {\n if (this.isConjunctiveFacet(facet)) {\n return this;\n }\n\n return this.setQueryParameters({\n facets: this.facets.concat([facet])\n });\n },\n /**\n * Add a disjunctive facet to the disjunctiveFacets attribute of the helper\n * configuration, if it isn't already present.\n * @method\n * @param {string} facet disjunctive facet name to add\n * @return {SearchParameters}\n */\n addDisjunctiveFacet: function addDisjunctiveFacet(facet) {\n if (this.isDisjunctiveFacet(facet)) {\n return this;\n }\n\n return this.setQueryParameters({\n disjunctiveFacets: this.disjunctiveFacets.concat([facet])\n });\n },\n /**\n * Add a hierarchical facet to the hierarchicalFacets attribute of the helper\n * configuration.\n * @method\n * @param {object} hierarchicalFacet hierarchical facet to add\n * @return {SearchParameters}\n * @throws will throw an error if a hierarchical facet with the same name was already declared\n */\n addHierarchicalFacet: function addHierarchicalFacet(hierarchicalFacet) {\n if (this.isHierarchicalFacet(hierarchicalFacet.name)) {\n throw new Error(\n 'Cannot declare two hierarchical facets with the same name: `' + hierarchicalFacet.name + '`');\n }\n\n return this.setQueryParameters({\n hierarchicalFacets: this.hierarchicalFacets.concat([hierarchicalFacet])\n });\n },\n /**\n * Add a refinement on a \"normal\" facet\n * @method\n * @param {string} facet attribute to apply the faceting on\n * @param {string} value value of the attribute (will be converted to string)\n * @return {SearchParameters}\n */\n addFacetRefinement: function addFacetRefinement(facet, value) {\n if (!this.isConjunctiveFacet(facet)) {\n throw new Error(facet + ' is not defined in the facets attribute of the helper configuration');\n }\n if (RefinementList.isRefined(this.facetsRefinements, facet, value)) return this;\n\n return this.setQueryParameters({\n facetsRefinements: RefinementList.addRefinement(this.facetsRefinements, facet, value)\n });\n },\n /**\n * Exclude a value from a \"normal\" facet\n * @method\n * @param {string} facet attribute to apply the exclusion on\n * @param {string} value value of the attribute (will be converted to string)\n * @return {SearchParameters}\n */\n addExcludeRefinement: function addExcludeRefinement(facet, value) {\n if (!this.isConjunctiveFacet(facet)) {\n throw new Error(facet + ' is not defined in the facets attribute of the helper configuration');\n }\n if (RefinementList.isRefined(this.facetsExcludes, facet, value)) return this;\n\n return this.setQueryParameters({\n facetsExcludes: RefinementList.addRefinement(this.facetsExcludes, facet, value)\n });\n },\n /**\n * Adds a refinement on a disjunctive facet.\n * @method\n * @param {string} facet attribute to apply the faceting on\n * @param {string} value value of the attribute (will be converted to string)\n * @return {SearchParameters}\n */\n addDisjunctiveFacetRefinement: function addDisjunctiveFacetRefinement(facet, value) {\n if (!this.isDisjunctiveFacet(facet)) {\n throw new Error(\n facet + ' is not defined in the disjunctiveFacets attribute of the helper configuration');\n }\n\n if (RefinementList.isRefined(this.disjunctiveFacetsRefinements, facet, value)) return this;\n\n return this.setQueryParameters({\n disjunctiveFacetsRefinements: RefinementList.addRefinement(\n this.disjunctiveFacetsRefinements, facet, value)\n });\n },\n /**\n * addTagRefinement adds a tag to the list used to filter the results\n * @param {string} tag tag to be added\n * @return {SearchParameters}\n */\n addTagRefinement: function addTagRefinement(tag) {\n if (this.isTagRefined(tag)) return this;\n\n var modification = {\n tagRefinements: this.tagRefinements.concat(tag)\n };\n\n return this.setQueryParameters(modification);\n },\n /**\n * Remove a facet from the facets attribute of the helper configuration, if it\n * is present.\n * @method\n * @param {string} facet facet name to remove\n * @return {SearchParameters}\n */\n removeFacet: function removeFacet(facet) {\n if (!this.isConjunctiveFacet(facet)) {\n return this;\n }\n\n return this.clearRefinements(facet).setQueryParameters({\n facets: this.facets.filter(function(f) {\n return f !== facet;\n })\n });\n },\n /**\n * Remove a disjunctive facet from the disjunctiveFacets attribute of the\n * helper configuration, if it is present.\n * @method\n * @param {string} facet disjunctive facet name to remove\n * @return {SearchParameters}\n */\n removeDisjunctiveFacet: function removeDisjunctiveFacet(facet) {\n if (!this.isDisjunctiveFacet(facet)) {\n return this;\n }\n\n return this.clearRefinements(facet).setQueryParameters({\n disjunctiveFacets: this.disjunctiveFacets.filter(function(f) {\n return f !== facet;\n })\n });\n },\n /**\n * Remove a hierarchical facet from the hierarchicalFacets attribute of the\n * helper configuration, if it is present.\n * @method\n * @param {string} facet hierarchical facet name to remove\n * @return {SearchParameters}\n */\n removeHierarchicalFacet: function removeHierarchicalFacet(facet) {\n if (!this.isHierarchicalFacet(facet)) {\n return this;\n }\n\n return this.clearRefinements(facet).setQueryParameters({\n hierarchicalFacets: this.hierarchicalFacets.filter(function(f) {\n return f.name !== facet;\n })\n });\n },\n /**\n * Remove a refinement set on facet. If a value is provided, it will clear the\n * refinement for the given value, otherwise it will clear all the refinement\n * values for the faceted attribute.\n * @method\n * @param {string} facet name of the attribute used for faceting\n * @param {string} [value] value used to filter\n * @return {SearchParameters}\n */\n removeFacetRefinement: function removeFacetRefinement(facet, value) {\n if (!this.isConjunctiveFacet(facet)) {\n throw new Error(facet + ' is not defined in the facets attribute of the helper configuration');\n }\n if (!RefinementList.isRefined(this.facetsRefinements, facet, value)) return this;\n\n return this.setQueryParameters({\n facetsRefinements: RefinementList.removeRefinement(this.facetsRefinements, facet, value)\n });\n },\n /**\n * Remove a negative refinement on a facet\n * @method\n * @param {string} facet name of the attribute used for faceting\n * @param {string} value value used to filter\n * @return {SearchParameters}\n */\n removeExcludeRefinement: function removeExcludeRefinement(facet, value) {\n if (!this.isConjunctiveFacet(facet)) {\n throw new Error(facet + ' is not defined in the facets attribute of the helper configuration');\n }\n if (!RefinementList.isRefined(this.facetsExcludes, facet, value)) return this;\n\n return this.setQueryParameters({\n facetsExcludes: RefinementList.removeRefinement(this.facetsExcludes, facet, value)\n });\n },\n /**\n * Remove a refinement on a disjunctive facet\n * @method\n * @param {string} facet name of the attribute used for faceting\n * @param {string} value value used to filter\n * @return {SearchParameters}\n */\n removeDisjunctiveFacetRefinement: function removeDisjunctiveFacetRefinement(facet, value) {\n if (!this.isDisjunctiveFacet(facet)) {\n throw new Error(\n facet + ' is not defined in the disjunctiveFacets attribute of the helper configuration');\n }\n if (!RefinementList.isRefined(this.disjunctiveFacetsRefinements, facet, value)) return this;\n\n return this.setQueryParameters({\n disjunctiveFacetsRefinements: RefinementList.removeRefinement(\n this.disjunctiveFacetsRefinements, facet, value)\n });\n },\n /**\n * Remove a tag from the list of tag refinements\n * @method\n * @param {string} tag the tag to remove\n * @return {SearchParameters}\n */\n removeTagRefinement: function removeTagRefinement(tag) {\n if (!this.isTagRefined(tag)) return this;\n\n var modification = {\n tagRefinements: this.tagRefinements.filter(function(t) {\n return t !== tag;\n })\n };\n\n return this.setQueryParameters(modification);\n },\n /**\n * Generic toggle refinement method to use with facet, disjunctive facets\n * and hierarchical facets\n * @param {string} facet the facet to refine\n * @param {string} value the associated value\n * @return {SearchParameters}\n * @throws will throw an error if the facet is not declared in the settings of the helper\n * @deprecated since version 2.19.0, see {@link SearchParameters#toggleFacetRefinement}\n */\n toggleRefinement: function toggleRefinement(facet, value) {\n return this.toggleFacetRefinement(facet, value);\n },\n /**\n * Generic toggle refinement method to use with facet, disjunctive facets\n * and hierarchical facets\n * @param {string} facet the facet to refine\n * @param {string} value the associated value\n * @return {SearchParameters}\n * @throws will throw an error if the facet is not declared in the settings of the helper\n */\n toggleFacetRefinement: function toggleFacetRefinement(facet, value) {\n if (this.isHierarchicalFacet(facet)) {\n return this.toggleHierarchicalFacetRefinement(facet, value);\n } else if (this.isConjunctiveFacet(facet)) {\n return this.toggleConjunctiveFacetRefinement(facet, value);\n } else if (this.isDisjunctiveFacet(facet)) {\n return this.toggleDisjunctiveFacetRefinement(facet, value);\n }\n\n throw new Error('Cannot refine the undeclared facet ' + facet +\n '; it should be added to the helper options facets, disjunctiveFacets or hierarchicalFacets');\n },\n /**\n * Switch the refinement applied over a facet/value\n * @method\n * @param {string} facet name of the attribute used for faceting\n * @param {value} value value used for filtering\n * @return {SearchParameters}\n */\n toggleConjunctiveFacetRefinement: function toggleConjunctiveFacetRefinement(facet, value) {\n if (!this.isConjunctiveFacet(facet)) {\n throw new Error(facet + ' is not defined in the facets attribute of the helper configuration');\n }\n\n return this.setQueryParameters({\n facetsRefinements: RefinementList.toggleRefinement(this.facetsRefinements, facet, value)\n });\n },\n /**\n * Switch the refinement applied over a facet/value\n * @method\n * @param {string} facet name of the attribute used for faceting\n * @param {value} value value used for filtering\n * @return {SearchParameters}\n */\n toggleExcludeFacetRefinement: function toggleExcludeFacetRefinement(facet, value) {\n if (!this.isConjunctiveFacet(facet)) {\n throw new Error(facet + ' is not defined in the facets attribute of the helper configuration');\n }\n\n return this.setQueryParameters({\n facetsExcludes: RefinementList.toggleRefinement(this.facetsExcludes, facet, value)\n });\n },\n /**\n * Switch the refinement applied over a facet/value\n * @method\n * @param {string} facet name of the attribute used for faceting\n * @param {value} value value used for filtering\n * @return {SearchParameters}\n */\n toggleDisjunctiveFacetRefinement: function toggleDisjunctiveFacetRefinement(facet, value) {\n if (!this.isDisjunctiveFacet(facet)) {\n throw new Error(\n facet + ' is not defined in the disjunctiveFacets attribute of the helper configuration');\n }\n\n return this.setQueryParameters({\n disjunctiveFacetsRefinements: RefinementList.toggleRefinement(\n this.disjunctiveFacetsRefinements, facet, value)\n });\n },\n /**\n * Switch the refinement applied over a facet/value\n * @method\n * @param {string} facet name of the attribute used for faceting\n * @param {value} value value used for filtering\n * @return {SearchParameters}\n */\n toggleHierarchicalFacetRefinement: function toggleHierarchicalFacetRefinement(facet, value) {\n if (!this.isHierarchicalFacet(facet)) {\n throw new Error(\n facet + ' is not defined in the hierarchicalFacets attribute of the helper configuration');\n }\n\n var separator = this._getHierarchicalFacetSeparator(this.getHierarchicalFacetByName(facet));\n\n var mod = {};\n\n var upOneOrMultipleLevel = this.hierarchicalFacetsRefinements[facet] !== undefined &&\n this.hierarchicalFacetsRefinements[facet].length > 0 && (\n // remove current refinement:\n // refinement was 'beer > IPA', call is toggleRefine('beer > IPA'), refinement should be `beer`\n this.hierarchicalFacetsRefinements[facet][0] === value ||\n // remove a parent refinement of the current refinement:\n // - refinement was 'beer > IPA > Flying dog'\n // - call is toggleRefine('beer > IPA')\n // - refinement should be `beer`\n this.hierarchicalFacetsRefinements[facet][0].indexOf(value + separator) === 0\n );\n\n if (upOneOrMultipleLevel) {\n if (value.indexOf(separator) === -1) {\n // go back to root level\n mod[facet] = [];\n } else {\n mod[facet] = [value.slice(0, value.lastIndexOf(separator))];\n }\n } else {\n mod[facet] = [value];\n }\n\n return this.setQueryParameters({\n hierarchicalFacetsRefinements: defaultsPure({}, mod, this.hierarchicalFacetsRefinements)\n });\n },\n\n /**\n * Adds a refinement on a hierarchical facet.\n * @param {string} facet the facet name\n * @param {string} path the hierarchical facet path\n * @return {SearchParameter} the new state\n * @throws Error if the facet is not defined or if the facet is refined\n */\n addHierarchicalFacetRefinement: function(facet, path) {\n if (this.isHierarchicalFacetRefined(facet)) {\n throw new Error(facet + ' is already refined.');\n }\n if (!this.isHierarchicalFacet(facet)) {\n throw new Error(facet + ' is not defined in the hierarchicalFacets attribute of the helper configuration.');\n }\n var mod = {};\n mod[facet] = [path];\n return this.setQueryParameters({\n hierarchicalFacetsRefinements: defaultsPure({}, mod, this.hierarchicalFacetsRefinements)\n });\n },\n\n /**\n * Removes the refinement set on a hierarchical facet.\n * @param {string} facet the facet name\n * @return {SearchParameter} the new state\n * @throws Error if the facet is not defined or if the facet is not refined\n */\n removeHierarchicalFacetRefinement: function(facet) {\n if (!this.isHierarchicalFacetRefined(facet)) {\n return this;\n }\n var mod = {};\n mod[facet] = [];\n return this.setQueryParameters({\n hierarchicalFacetsRefinements: defaultsPure({}, mod, this.hierarchicalFacetsRefinements)\n });\n },\n /**\n * Switch the tag refinement\n * @method\n * @param {string} tag the tag to remove or add\n * @return {SearchParameters}\n */\n toggleTagRefinement: function toggleTagRefinement(tag) {\n if (this.isTagRefined(tag)) {\n return this.removeTagRefinement(tag);\n }\n\n return this.addTagRefinement(tag);\n },\n /**\n * Test if the facet name is from one of the disjunctive facets\n * @method\n * @param {string} facet facet name to test\n * @return {boolean}\n */\n isDisjunctiveFacet: function(facet) {\n return this.disjunctiveFacets.indexOf(facet) > -1;\n },\n /**\n * Test if the facet name is from one of the hierarchical facets\n * @method\n * @param {string} facetName facet name to test\n * @return {boolean}\n */\n isHierarchicalFacet: function(facetName) {\n return this.getHierarchicalFacetByName(facetName) !== undefined;\n },\n /**\n * Test if the facet name is from one of the conjunctive/normal facets\n * @method\n * @param {string} facet facet name to test\n * @return {boolean}\n */\n isConjunctiveFacet: function(facet) {\n return this.facets.indexOf(facet) > -1;\n },\n /**\n * Returns true if the facet is refined, either for a specific value or in\n * general.\n * @method\n * @param {string} facet name of the attribute for used for faceting\n * @param {string} value, optional value. If passed will test that this value\n * is filtering the given facet.\n * @return {boolean} returns true if refined\n */\n isFacetRefined: function isFacetRefined(facet, value) {\n if (!this.isConjunctiveFacet(facet)) {\n return false;\n }\n return RefinementList.isRefined(this.facetsRefinements, facet, value);\n },\n /**\n * Returns true if the facet contains exclusions or if a specific value is\n * excluded.\n *\n * @method\n * @param {string} facet name of the attribute for used for faceting\n * @param {string} [value] optional value. If passed will test that this value\n * is filtering the given facet.\n * @return {boolean} returns true if refined\n */\n isExcludeRefined: function isExcludeRefined(facet, value) {\n if (!this.isConjunctiveFacet(facet)) {\n return false;\n }\n return RefinementList.isRefined(this.facetsExcludes, facet, value);\n },\n /**\n * Returns true if the facet contains a refinement, or if a value passed is a\n * refinement for the facet.\n * @method\n * @param {string} facet name of the attribute for used for faceting\n * @param {string} value optional, will test if the value is used for refinement\n * if there is one, otherwise will test if the facet contains any refinement\n * @return {boolean}\n */\n isDisjunctiveFacetRefined: function isDisjunctiveFacetRefined(facet, value) {\n if (!this.isDisjunctiveFacet(facet)) {\n return false;\n }\n return RefinementList.isRefined(this.disjunctiveFacetsRefinements, facet, value);\n },\n /**\n * Returns true if the facet contains a refinement, or if a value passed is a\n * refinement for the facet.\n * @method\n * @param {string} facet name of the attribute for used for faceting\n * @param {string} value optional, will test if the value is used for refinement\n * if there is one, otherwise will test if the facet contains any refinement\n * @return {boolean}\n */\n isHierarchicalFacetRefined: function isHierarchicalFacetRefined(facet, value) {\n if (!this.isHierarchicalFacet(facet)) {\n return false;\n }\n\n var refinements = this.getHierarchicalRefinement(facet);\n\n if (!value) {\n return refinements.length > 0;\n }\n\n return refinements.indexOf(value) !== -1;\n },\n /**\n * Test if the triple (attribute, operator, value) is already refined.\n * If only the attribute and the operator are provided, it tests if the\n * contains any refinement value.\n * @method\n * @param {string} attribute attribute for which the refinement is applied\n * @param {string} [operator] operator of the refinement\n * @param {string} [value] value of the refinement\n * @return {boolean} true if it is refined\n */\n isNumericRefined: function isNumericRefined(attribute, operator, value) {\n if (value === undefined && operator === undefined) {\n return !!this.numericRefinements[attribute];\n }\n\n var isOperatorDefined =\n this.numericRefinements[attribute] &&\n this.numericRefinements[attribute][operator] !== undefined;\n\n if (value === undefined || !isOperatorDefined) {\n return isOperatorDefined;\n }\n\n var parsedValue = valToNumber(value);\n var isAttributeValueDefined =\n findArray(this.numericRefinements[attribute][operator], parsedValue) !==\n undefined;\n\n return isOperatorDefined && isAttributeValueDefined;\n },\n /**\n * Returns true if the tag refined, false otherwise\n * @method\n * @param {string} tag the tag to check\n * @return {boolean}\n */\n isTagRefined: function isTagRefined(tag) {\n return this.tagRefinements.indexOf(tag) !== -1;\n },\n /**\n * Returns the list of all disjunctive facets refined\n * @method\n * @param {string} facet name of the attribute used for faceting\n * @param {value} value value used for filtering\n * @return {string[]}\n */\n getRefinedDisjunctiveFacets: function getRefinedDisjunctiveFacets() {\n var self = this;\n\n // attributes used for numeric filter can also be disjunctive\n var disjunctiveNumericRefinedFacets = intersection(\n Object.keys(this.numericRefinements).filter(function(facet) {\n return Object.keys(self.numericRefinements[facet]).length > 0;\n }),\n this.disjunctiveFacets\n );\n\n return Object.keys(this.disjunctiveFacetsRefinements).filter(function(facet) {\n return self.disjunctiveFacetsRefinements[facet].length > 0;\n })\n .concat(disjunctiveNumericRefinedFacets)\n .concat(this.getRefinedHierarchicalFacets());\n },\n /**\n * Returns the list of all disjunctive facets refined\n * @method\n * @param {string} facet name of the attribute used for faceting\n * @param {value} value value used for filtering\n * @return {string[]}\n */\n getRefinedHierarchicalFacets: function getRefinedHierarchicalFacets() {\n var self = this;\n return intersection(\n // enforce the order between the two arrays,\n // so that refinement name index === hierarchical facet index\n this.hierarchicalFacets.map(function(facet) { return facet.name; }),\n Object.keys(this.hierarchicalFacetsRefinements).filter(function(facet) {\n return self.hierarchicalFacetsRefinements[facet].length > 0;\n })\n );\n },\n /**\n * Returned the list of all disjunctive facets not refined\n * @method\n * @return {string[]}\n */\n getUnrefinedDisjunctiveFacets: function() {\n var refinedFacets = this.getRefinedDisjunctiveFacets();\n\n return this.disjunctiveFacets.filter(function(f) {\n return refinedFacets.indexOf(f) === -1;\n });\n },\n\n managedParameters: [\n 'index',\n 'facets', 'disjunctiveFacets', 'facetsRefinements',\n 'facetsExcludes', 'disjunctiveFacetsRefinements',\n 'numericRefinements', 'tagRefinements', 'hierarchicalFacets', 'hierarchicalFacetsRefinements'\n ],\n getQueryParams: function getQueryParams() {\n var managedParameters = this.managedParameters;\n\n var queryParams = {};\n\n var self = this;\n Object.keys(this).forEach(function(paramName) {\n var paramValue = self[paramName];\n if (managedParameters.indexOf(paramName) === -1 && paramValue !== undefined) {\n queryParams[paramName] = paramValue;\n }\n });\n\n return queryParams;\n },\n /**\n * Let the user set a specific value for a given parameter. Will return the\n * same instance if the parameter is invalid or if the value is the same as the\n * previous one.\n * @method\n * @param {string} parameter the parameter name\n * @param {any} value the value to be set, must be compliant with the definition\n * of the attribute on the object\n * @return {SearchParameters} the updated state\n */\n setQueryParameter: function setParameter(parameter, value) {\n if (this[parameter] === value) return this;\n\n var modification = {};\n\n modification[parameter] = value;\n\n return this.setQueryParameters(modification);\n },\n /**\n * Let the user set any of the parameters with a plain object.\n * @method\n * @param {object} params all the keys and the values to be updated\n * @return {SearchParameters} a new updated instance\n */\n setQueryParameters: function setQueryParameters(params) {\n if (!params) return this;\n\n var error = SearchParameters.validate(this, params);\n\n if (error) {\n throw error;\n }\n\n var self = this;\n var nextWithNumbers = SearchParameters._parseNumbers(params);\n var previousPlainObject = Object.keys(this).reduce(function(acc, key) {\n acc[key] = self[key];\n return acc;\n }, {});\n\n var nextPlainObject = Object.keys(nextWithNumbers).reduce(\n function(previous, key) {\n var isPreviousValueDefined = previous[key] !== undefined;\n var isNextValueDefined = nextWithNumbers[key] !== undefined;\n\n if (isPreviousValueDefined && !isNextValueDefined) {\n return omit(previous, [key]);\n }\n\n if (isNextValueDefined) {\n previous[key] = nextWithNumbers[key];\n }\n\n return previous;\n },\n previousPlainObject\n );\n\n return new this.constructor(nextPlainObject);\n },\n\n /**\n * Returns a new instance with the page reset. Two scenarios possible:\n * the page is omitted -> return the given instance\n * the page is set -> return a new instance with a page of 0\n * @return {SearchParameters} a new updated instance\n */\n resetPage: function() {\n if (this.page === undefined) {\n return this;\n }\n\n return this.setPage(0);\n },\n\n /**\n * Helper function to get the hierarchicalFacet separator or the default one (`>`)\n * @param {object} hierarchicalFacet\n * @return {string} returns the hierarchicalFacet.separator or `>` as default\n */\n _getHierarchicalFacetSortBy: function(hierarchicalFacet) {\n return hierarchicalFacet.sortBy || ['isRefined:desc', 'name:asc'];\n },\n\n /**\n * Helper function to get the hierarchicalFacet separator or the default one (`>`)\n * @private\n * @param {object} hierarchicalFacet\n * @return {string} returns the hierarchicalFacet.separator or `>` as default\n */\n _getHierarchicalFacetSeparator: function(hierarchicalFacet) {\n return hierarchicalFacet.separator || ' > ';\n },\n\n /**\n * Helper function to get the hierarchicalFacet prefix path or null\n * @private\n * @param {object} hierarchicalFacet\n * @return {string} returns the hierarchicalFacet.rootPath or null as default\n */\n _getHierarchicalRootPath: function(hierarchicalFacet) {\n return hierarchicalFacet.rootPath || null;\n },\n\n /**\n * Helper function to check if we show the parent level of the hierarchicalFacet\n * @private\n * @param {object} hierarchicalFacet\n * @return {string} returns the hierarchicalFacet.showParentLevel or true as default\n */\n _getHierarchicalShowParentLevel: function(hierarchicalFacet) {\n if (typeof hierarchicalFacet.showParentLevel === 'boolean') {\n return hierarchicalFacet.showParentLevel;\n }\n return true;\n },\n\n /**\n * Helper function to get the hierarchicalFacet by it's name\n * @param {string} hierarchicalFacetName\n * @return {object} a hierarchicalFacet\n */\n getHierarchicalFacetByName: function(hierarchicalFacetName) {\n return find(\n this.hierarchicalFacets,\n function(f) {\n return f.name === hierarchicalFacetName;\n }\n );\n },\n\n /**\n * Get the current breadcrumb for a hierarchical facet, as an array\n * @param {string} facetName Hierarchical facet name\n * @return {array.} the path as an array of string\n */\n getHierarchicalFacetBreadcrumb: function(facetName) {\n if (!this.isHierarchicalFacet(facetName)) {\n return [];\n }\n\n var refinement = this.getHierarchicalRefinement(facetName)[0];\n if (!refinement) return [];\n\n var separator = this._getHierarchicalFacetSeparator(\n this.getHierarchicalFacetByName(facetName)\n );\n var path = refinement.split(separator);\n return path.map(function(part) {\n return part.trim();\n });\n },\n\n toString: function() {\n return JSON.stringify(this, null, 2);\n }\n};\n\n/**\n * Callback used for clearRefinement method\n * @callback SearchParameters.clearCallback\n * @param {OperatorList|FacetList} value the value of the filter\n * @param {string} key the current attribute name\n * @param {string} type `numeric`, `disjunctiveFacet`, `conjunctiveFacet`, `hierarchicalFacet` or `exclude`\n * depending on the type of facet\n * @return {boolean} `true` if the element should be removed. `false` otherwise.\n */\nmodule.exports = SearchParameters;\n","'use strict';\n\nfunction compareAscending(value, other) {\n if (value !== other) {\n var valIsDefined = value !== undefined;\n var valIsNull = value === null;\n\n var othIsDefined = other !== undefined;\n var othIsNull = other === null;\n\n if (\n (!othIsNull && value > other) ||\n (valIsNull && othIsDefined) ||\n !valIsDefined\n ) {\n return 1;\n }\n if (\n (!valIsNull && value < other) ||\n (othIsNull && valIsDefined) ||\n !othIsDefined\n ) {\n return -1;\n }\n }\n return 0;\n}\n\n/**\n * @param {Array} collection object with keys in attributes\n * @param {Array} iteratees attributes\n * @param {Array} orders asc | desc\n */\nfunction orderBy(collection, iteratees, orders) {\n if (!Array.isArray(collection)) {\n return [];\n }\n\n if (!Array.isArray(orders)) {\n orders = [];\n }\n\n var result = collection.map(function(value, index) {\n return {\n criteria: iteratees.map(function(iteratee) {\n return value[iteratee];\n }),\n index: index,\n value: value\n };\n });\n\n result.sort(function comparer(object, other) {\n var index = -1;\n\n while (++index < object.criteria.length) {\n var res = compareAscending(object.criteria[index], other.criteria[index]);\n if (res) {\n if (index >= orders.length) {\n return res;\n }\n if (orders[index] === 'desc') {\n return -res;\n }\n return res;\n }\n }\n\n // This ensures a stable sort in V8 and other engines.\n // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.\n return object.index - other.index;\n });\n\n return result.map(function(res) {\n return res.value;\n });\n}\n\nmodule.exports = orderBy;\n","'use strict';\n\nmodule.exports = function compact(array) {\n if (!Array.isArray(array)) {\n return [];\n }\n\n return array.filter(Boolean);\n};\n","'use strict';\n\n// @MAJOR can be replaced by native Array#findIndex when we change support\nmodule.exports = function find(array, comparator) {\n if (!Array.isArray(array)) {\n return -1;\n }\n\n for (var i = 0; i < array.length; i++) {\n if (comparator(array[i])) {\n return i;\n }\n }\n return -1;\n};\n","'use strict';\n\nvar find = require('./find');\n\n/**\n * Transform sort format from user friendly notation to lodash format\n * @param {string[]} sortBy array of predicate of the form \"attribute:order\"\n * @param {string[]} [defaults] array of predicate of the form \"attribute:order\"\n * @return {array.} array containing 2 elements : attributes, orders\n */\nmodule.exports = function formatSort(sortBy, defaults) {\n var defaultInstructions = (defaults || []).map(function(sort) {\n return sort.split(':');\n });\n\n return sortBy.reduce(\n function preparePredicate(out, sort) {\n var sortInstruction = sort.split(':');\n\n var matchingDefault = find(defaultInstructions, function(\n defaultInstruction\n ) {\n return defaultInstruction[0] === sortInstruction[0];\n });\n\n if (sortInstruction.length > 1 || !matchingDefault) {\n out[0].push(sortInstruction[0]);\n out[1].push(sortInstruction[1]);\n return out;\n }\n\n out[0].push(matchingDefault[0]);\n out[1].push(matchingDefault[1]);\n return out;\n },\n [[], []]\n );\n};\n","'use strict';\n\nmodule.exports = generateTrees;\n\nvar orderBy = require('../functions/orderBy');\nvar find = require('../functions/find');\nvar prepareHierarchicalFacetSortBy = require('../functions/formatSort');\n\nfunction generateTrees(state) {\n return function generate(hierarchicalFacetResult, hierarchicalFacetIndex) {\n var hierarchicalFacet = state.hierarchicalFacets[hierarchicalFacetIndex];\n var hierarchicalFacetRefinement =\n (state.hierarchicalFacetsRefinements[hierarchicalFacet.name] &&\n state.hierarchicalFacetsRefinements[hierarchicalFacet.name][0]) ||\n '';\n var hierarchicalSeparator = state._getHierarchicalFacetSeparator(\n hierarchicalFacet\n );\n var hierarchicalRootPath = state._getHierarchicalRootPath(\n hierarchicalFacet\n );\n var hierarchicalShowParentLevel = state._getHierarchicalShowParentLevel(\n hierarchicalFacet\n );\n var sortBy = prepareHierarchicalFacetSortBy(\n state._getHierarchicalFacetSortBy(hierarchicalFacet)\n );\n\n var rootExhaustive = hierarchicalFacetResult.every(function(facetResult) {\n return facetResult.exhaustive;\n });\n\n var generateTreeFn = generateHierarchicalTree(\n sortBy,\n hierarchicalSeparator,\n hierarchicalRootPath,\n hierarchicalShowParentLevel,\n hierarchicalFacetRefinement\n );\n\n var results = hierarchicalFacetResult;\n\n if (hierarchicalRootPath) {\n results = hierarchicalFacetResult.slice(\n hierarchicalRootPath.split(hierarchicalSeparator).length\n );\n }\n\n return results.reduce(generateTreeFn, {\n name: state.hierarchicalFacets[hierarchicalFacetIndex].name,\n count: null, // root level, no count\n isRefined: true, // root level, always refined\n path: null, // root level, no path\n exhaustive: rootExhaustive,\n data: null\n });\n };\n}\n\nfunction generateHierarchicalTree(\n sortBy,\n hierarchicalSeparator,\n hierarchicalRootPath,\n hierarchicalShowParentLevel,\n currentRefinement\n) {\n return function generateTree(\n hierarchicalTree,\n hierarchicalFacetResult,\n currentHierarchicalLevel\n ) {\n var parent = hierarchicalTree;\n\n if (currentHierarchicalLevel > 0) {\n var level = 0;\n\n parent = hierarchicalTree;\n\n while (level < currentHierarchicalLevel) {\n /**\n * @type {object[]]} hierarchical data\n */\n var data = parent && Array.isArray(parent.data) ? parent.data : [];\n parent = find(data, function(subtree) {\n return subtree.isRefined;\n });\n level++;\n }\n }\n\n // we found a refined parent, let's add current level data under it\n if (parent) {\n // filter values in case an object has multiple categories:\n // {\n // categories: {\n // level0: ['beers', 'bières'],\n // level1: ['beers > IPA', 'bières > Belges']\n // }\n // }\n //\n // If parent refinement is `beers`, then we do not want to have `bières > Belges`\n // showing up\n\n var picked = Object.keys(hierarchicalFacetResult.data)\n .map(function(facetValue) {\n return [facetValue, hierarchicalFacetResult.data[facetValue]];\n })\n .filter(function(tuple) {\n var facetValue = tuple[0];\n return onlyMatchingTree(\n facetValue,\n parent.path || hierarchicalRootPath,\n currentRefinement,\n hierarchicalSeparator,\n hierarchicalRootPath,\n hierarchicalShowParentLevel\n );\n });\n\n parent.data = orderBy(\n picked.map(function(tuple) {\n var facetValue = tuple[0];\n var facetCount = tuple[1];\n\n return format(\n facetCount,\n facetValue,\n hierarchicalSeparator,\n currentRefinement,\n hierarchicalFacetResult.exhaustive\n );\n }),\n sortBy[0],\n sortBy[1]\n );\n }\n\n return hierarchicalTree;\n };\n}\n\nfunction onlyMatchingTree(\n facetValue,\n parentPath,\n currentRefinement,\n hierarchicalSeparator,\n hierarchicalRootPath,\n hierarchicalShowParentLevel\n) {\n // we want the facetValue is a child of hierarchicalRootPath\n if (\n hierarchicalRootPath &&\n (facetValue.indexOf(hierarchicalRootPath) !== 0 ||\n hierarchicalRootPath === facetValue)\n ) {\n return false;\n }\n\n // we always want root levels (only when there is no prefix path)\n return (\n (!hierarchicalRootPath &&\n facetValue.indexOf(hierarchicalSeparator) === -1) ||\n // if there is a rootPath, being root level mean 1 level under rootPath\n (hierarchicalRootPath &&\n facetValue.split(hierarchicalSeparator).length -\n hierarchicalRootPath.split(hierarchicalSeparator).length ===\n 1) ||\n // if current refinement is a root level and current facetValue is a root level,\n // keep the facetValue\n (facetValue.indexOf(hierarchicalSeparator) === -1 &&\n currentRefinement.indexOf(hierarchicalSeparator) === -1) ||\n // currentRefinement is a child of the facet value\n currentRefinement.indexOf(facetValue) === 0 ||\n // facetValue is a child of the current parent, add it\n (facetValue.indexOf(parentPath + hierarchicalSeparator) === 0 &&\n (hierarchicalShowParentLevel ||\n facetValue.indexOf(currentRefinement) === 0))\n );\n}\n\nfunction format(\n facetCount,\n facetValue,\n hierarchicalSeparator,\n currentRefinement,\n exhaustive\n) {\n var parts = facetValue.split(hierarchicalSeparator);\n return {\n name: parts[parts.length - 1].trim(),\n path: facetValue,\n count: facetCount,\n isRefined:\n currentRefinement === facetValue ||\n currentRefinement.indexOf(facetValue + hierarchicalSeparator) === 0,\n exhaustive: exhaustive,\n data: null\n };\n}\n","'use strict';\n\nvar merge = require('../functions/merge');\nvar defaultsPure = require('../functions/defaultsPure');\nvar orderBy = require('../functions/orderBy');\nvar compact = require('../functions/compact');\nvar find = require('../functions/find');\nvar findIndex = require('../functions/findIndex');\nvar formatSort = require('../functions/formatSort');\n\nvar generateHierarchicalTree = require('./generate-hierarchical-tree');\n\n/**\n * @typedef SearchResults.Facet\n * @type {object}\n * @property {string} name name of the attribute in the record\n * @property {object} data the faceting data: value, number of entries\n * @property {object} stats undefined unless facet_stats is retrieved from algolia\n */\n\n/**\n * @typedef SearchResults.HierarchicalFacet\n * @type {object}\n * @property {string} name name of the current value given the hierarchical level, trimmed.\n * If root node, you get the facet name\n * @property {number} count number of objects matching this hierarchical value\n * @property {string} path the current hierarchical value full path\n * @property {boolean} isRefined `true` if the current value was refined, `false` otherwise\n * @property {HierarchicalFacet[]} data sub values for the current level\n */\n\n/**\n * @typedef SearchResults.FacetValue\n * @type {object}\n * @property {string} name the facet value itself\n * @property {number} count times this facet appears in the results\n * @property {boolean} isRefined is the facet currently selected\n * @property {boolean} isExcluded is the facet currently excluded (only for conjunctive facets)\n */\n\n/**\n * @typedef Refinement\n * @type {object}\n * @property {string} type the type of filter used:\n * `numeric`, `facet`, `exclude`, `disjunctive`, `hierarchical`\n * @property {string} attributeName name of the attribute used for filtering\n * @property {string} name the value of the filter\n * @property {number} numericValue the value as a number. Only for numeric filters.\n * @property {string} operator the operator used. Only for numeric filters.\n * @property {number} count the number of computed hits for this filter. Only on facets.\n * @property {boolean} exhaustive if the count is exhaustive\n */\n\n/**\n * @param {string[]} attributes\n */\nfunction getIndices(attributes) {\n var indices = {};\n\n attributes.forEach(function(val, idx) {\n indices[val] = idx;\n });\n\n return indices;\n}\n\nfunction assignFacetStats(dest, facetStats, key) {\n if (facetStats && facetStats[key]) {\n dest.stats = facetStats[key];\n }\n}\n\n/**\n * @typedef {Object} HierarchicalFacet\n * @property {string} name\n * @property {string[]} attributes\n */\n\n/**\n * @param {HierarchicalFacet[]} hierarchicalFacets\n * @param {string} hierarchicalAttributeName\n */\nfunction findMatchingHierarchicalFacetFromAttributeName(\n hierarchicalFacets,\n hierarchicalAttributeName\n) {\n return find(hierarchicalFacets, function facetKeyMatchesAttribute(\n hierarchicalFacet\n ) {\n var facetNames = hierarchicalFacet.attributes || [];\n return facetNames.indexOf(hierarchicalAttributeName) > -1;\n });\n}\n\n/*eslint-disable */\n/**\n * Constructor for SearchResults\n * @class\n * @classdesc SearchResults contains the results of a query to Algolia using the\n * {@link AlgoliaSearchHelper}.\n * @param {SearchParameters} state state that led to the response\n * @param {array.} results the results from algolia client\n * @example SearchResults of the first query in\n * the instant search demo\n{\n \"hitsPerPage\": 10,\n \"processingTimeMS\": 2,\n \"facets\": [\n {\n \"name\": \"type\",\n \"data\": {\n \"HardGood\": 6627,\n \"BlackTie\": 550,\n \"Music\": 665,\n \"Software\": 131,\n \"Game\": 456,\n \"Movie\": 1571\n },\n \"exhaustive\": false\n },\n {\n \"exhaustive\": false,\n \"data\": {\n \"Free shipping\": 5507\n },\n \"name\": \"shipping\"\n }\n ],\n \"hits\": [\n {\n \"thumbnailImage\": \"http://img.bbystatic.com/BestBuy_US/images/products/1688/1688832_54x108_s.gif\",\n \"_highlightResult\": {\n \"shortDescription\": {\n \"matchLevel\": \"none\",\n \"value\": \"Safeguard your PC, Mac, Android and iOS devices with comprehensive Internet protection\",\n \"matchedWords\": []\n },\n \"category\": {\n \"matchLevel\": \"none\",\n \"value\": \"Computer Security Software\",\n \"matchedWords\": []\n },\n \"manufacturer\": {\n \"matchedWords\": [],\n \"value\": \"Webroot\",\n \"matchLevel\": \"none\"\n },\n \"name\": {\n \"value\": \"Webroot SecureAnywhere Internet Security (3-Device) (1-Year Subscription) - Mac/Windows\",\n \"matchedWords\": [],\n \"matchLevel\": \"none\"\n }\n },\n \"image\": \"http://img.bbystatic.com/BestBuy_US/images/products/1688/1688832_105x210_sc.jpg\",\n \"shipping\": \"Free shipping\",\n \"bestSellingRank\": 4,\n \"shortDescription\": \"Safeguard your PC, Mac, Android and iOS devices with comprehensive Internet protection\",\n \"url\": \"http://www.bestbuy.com/site/webroot-secureanywhere-internet-security-3-devi…d=1219060687969&skuId=1688832&cmp=RMX&ky=2d3GfEmNIzjA0vkzveHdZEBgpPCyMnLTJ\",\n \"name\": \"Webroot SecureAnywhere Internet Security (3-Device) (1-Year Subscription) - Mac/Windows\",\n \"category\": \"Computer Security Software\",\n \"salePrice_range\": \"1 - 50\",\n \"objectID\": \"1688832\",\n \"type\": \"Software\",\n \"customerReviewCount\": 5980,\n \"salePrice\": 49.99,\n \"manufacturer\": \"Webroot\"\n },\n ....\n ],\n \"nbHits\": 10000,\n \"disjunctiveFacets\": [\n {\n \"exhaustive\": false,\n \"data\": {\n \"5\": 183,\n \"12\": 112,\n \"7\": 149,\n ...\n },\n \"name\": \"customerReviewCount\",\n \"stats\": {\n \"max\": 7461,\n \"avg\": 157.939,\n \"min\": 1\n }\n },\n {\n \"data\": {\n \"Printer Ink\": 142,\n \"Wireless Speakers\": 60,\n \"Point & Shoot Cameras\": 48,\n ...\n },\n \"name\": \"category\",\n \"exhaustive\": false\n },\n {\n \"exhaustive\": false,\n \"data\": {\n \"> 5000\": 2,\n \"1 - 50\": 6524,\n \"501 - 2000\": 566,\n \"201 - 500\": 1501,\n \"101 - 200\": 1360,\n \"2001 - 5000\": 47\n },\n \"name\": \"salePrice_range\"\n },\n {\n \"data\": {\n \"Dynex™\": 202,\n \"Insignia™\": 230,\n \"PNY\": 72,\n ...\n },\n \"name\": \"manufacturer\",\n \"exhaustive\": false\n }\n ],\n \"query\": \"\",\n \"nbPages\": 100,\n \"page\": 0,\n \"index\": \"bestbuy\"\n}\n **/\n/*eslint-enable */\nfunction SearchResults(state, results) {\n var mainSubResponse = results[0];\n\n this._rawResults = results;\n\n var self = this;\n\n // https://www.algolia.com/doc/api-reference/api-methods/search/#response\n Object.keys(mainSubResponse).forEach(function(key) {\n self[key] = mainSubResponse[key];\n });\n\n /**\n * query used to generate the results\n * @name query\n * @member {string}\n * @memberof SearchResults\n * @instance\n */\n /**\n * The query as parsed by the engine given all the rules.\n * @name parsedQuery\n * @member {string}\n * @memberof SearchResults\n * @instance\n */\n /**\n * all the records that match the search parameters. Each record is\n * augmented with a new attribute `_highlightResult`\n * which is an object keyed by attribute and with the following properties:\n * - `value` : the value of the facet highlighted (html)\n * - `matchLevel`: full, partial or none depending on how the query terms match\n * @name hits\n * @member {object[]}\n * @memberof SearchResults\n * @instance\n */\n /**\n * index where the results come from\n * @name index\n * @member {string}\n * @memberof SearchResults\n * @instance\n */\n /**\n * number of hits per page requested\n * @name hitsPerPage\n * @member {number}\n * @memberof SearchResults\n * @instance\n */\n /**\n * total number of hits of this query on the index\n * @name nbHits\n * @member {number}\n * @memberof SearchResults\n * @instance\n */\n /**\n * total number of pages with respect to the number of hits per page and the total number of hits\n * @name nbPages\n * @member {number}\n * @memberof SearchResults\n * @instance\n */\n /**\n * current page\n * @name page\n * @member {number}\n * @memberof SearchResults\n * @instance\n */\n /**\n * The position if the position was guessed by IP.\n * @name aroundLatLng\n * @member {string}\n * @memberof SearchResults\n * @instance\n * @example \"48.8637,2.3615\",\n */\n /**\n * The radius computed by Algolia.\n * @name automaticRadius\n * @member {string}\n * @memberof SearchResults\n * @instance\n * @example \"126792922\",\n */\n /**\n * String identifying the server used to serve this request.\n *\n * getRankingInfo needs to be set to `true` for this to be returned\n *\n * @name serverUsed\n * @member {string}\n * @memberof SearchResults\n * @instance\n * @example \"c7-use-2.algolia.net\",\n */\n /**\n * Boolean that indicates if the computation of the counts did time out.\n * @deprecated\n * @name timeoutCounts\n * @member {boolean}\n * @memberof SearchResults\n * @instance\n */\n /**\n * Boolean that indicates if the computation of the hits did time out.\n * @deprecated\n * @name timeoutHits\n * @member {boolean}\n * @memberof SearchResults\n * @instance\n */\n /**\n * True if the counts of the facets is exhaustive\n * @name exhaustiveFacetsCount\n * @member {boolean}\n * @memberof SearchResults\n * @instance\n */\n /**\n * True if the number of hits is exhaustive\n * @name exhaustiveNbHits\n * @member {boolean}\n * @memberof SearchResults\n * @instance\n */\n /**\n * Contains the userData if they are set by a [query rule](https://www.algolia.com/doc/guides/query-rules/query-rules-overview/).\n * @name userData\n * @member {object[]}\n * @memberof SearchResults\n * @instance\n */\n /**\n * queryID is the unique identifier of the query used to generate the current search results.\n * This value is only available if the `clickAnalytics` search parameter is set to `true`.\n * @name queryID\n * @member {string}\n * @memberof SearchResults\n * @instance\n */\n\n /**\n * sum of the processing time of all the queries\n * @member {number}\n */\n this.processingTimeMS = results.reduce(function(sum, result) {\n return result.processingTimeMS === undefined\n ? sum\n : sum + result.processingTimeMS;\n }, 0);\n\n /**\n * disjunctive facets results\n * @member {SearchResults.Facet[]}\n */\n this.disjunctiveFacets = [];\n /**\n * disjunctive facets results\n * @member {SearchResults.HierarchicalFacet[]}\n */\n this.hierarchicalFacets = state.hierarchicalFacets.map(function initFutureTree() {\n return [];\n });\n /**\n * other facets results\n * @member {SearchResults.Facet[]}\n */\n this.facets = [];\n\n var disjunctiveFacets = state.getRefinedDisjunctiveFacets();\n\n var facetsIndices = getIndices(state.facets);\n var disjunctiveFacetsIndices = getIndices(state.disjunctiveFacets);\n var nextDisjunctiveResult = 1;\n\n // Since we send request only for disjunctive facets that have been refined,\n // we get the facets information from the first, general, response.\n\n var mainFacets = mainSubResponse.facets || {};\n\n Object.keys(mainFacets).forEach(function(facetKey) {\n var facetValueObject = mainFacets[facetKey];\n\n var hierarchicalFacet = findMatchingHierarchicalFacetFromAttributeName(\n state.hierarchicalFacets,\n facetKey\n );\n\n if (hierarchicalFacet) {\n // Place the hierarchicalFacet data at the correct index depending on\n // the attributes order that was defined at the helper initialization\n var facetIndex = hierarchicalFacet.attributes.indexOf(facetKey);\n var idxAttributeName = findIndex(state.hierarchicalFacets, function(f) {\n return f.name === hierarchicalFacet.name;\n });\n self.hierarchicalFacets[idxAttributeName][facetIndex] = {\n attribute: facetKey,\n data: facetValueObject,\n exhaustive: mainSubResponse.exhaustiveFacetsCount\n };\n } else {\n var isFacetDisjunctive = state.disjunctiveFacets.indexOf(facetKey) !== -1;\n var isFacetConjunctive = state.facets.indexOf(facetKey) !== -1;\n var position;\n\n if (isFacetDisjunctive) {\n position = disjunctiveFacetsIndices[facetKey];\n self.disjunctiveFacets[position] = {\n name: facetKey,\n data: facetValueObject,\n exhaustive: mainSubResponse.exhaustiveFacetsCount\n };\n assignFacetStats(self.disjunctiveFacets[position], mainSubResponse.facets_stats, facetKey);\n }\n if (isFacetConjunctive) {\n position = facetsIndices[facetKey];\n self.facets[position] = {\n name: facetKey,\n data: facetValueObject,\n exhaustive: mainSubResponse.exhaustiveFacetsCount\n };\n assignFacetStats(self.facets[position], mainSubResponse.facets_stats, facetKey);\n }\n }\n });\n\n // Make sure we do not keep holes within the hierarchical facets\n this.hierarchicalFacets = compact(this.hierarchicalFacets);\n\n // aggregate the refined disjunctive facets\n disjunctiveFacets.forEach(function(disjunctiveFacet) {\n var result = results[nextDisjunctiveResult];\n var facets = result && result.facets ? result.facets : {};\n var hierarchicalFacet = state.getHierarchicalFacetByName(disjunctiveFacet);\n\n // There should be only item in facets.\n Object.keys(facets).forEach(function(dfacet) {\n var facetResults = facets[dfacet];\n\n var position;\n\n if (hierarchicalFacet) {\n position = findIndex(state.hierarchicalFacets, function(f) {\n return f.name === hierarchicalFacet.name;\n });\n var attributeIndex = findIndex(self.hierarchicalFacets[position], function(f) {\n return f.attribute === dfacet;\n });\n\n // previous refinements and no results so not able to find it\n if (attributeIndex === -1) {\n return;\n }\n\n self.hierarchicalFacets[position][attributeIndex].data = merge(\n {},\n self.hierarchicalFacets[position][attributeIndex].data,\n facetResults\n );\n } else {\n position = disjunctiveFacetsIndices[dfacet];\n\n var dataFromMainRequest = mainSubResponse.facets && mainSubResponse.facets[dfacet] || {};\n\n self.disjunctiveFacets[position] = {\n name: dfacet,\n data: defaultsPure({}, facetResults, dataFromMainRequest),\n exhaustive: result.exhaustiveFacetsCount\n };\n assignFacetStats(self.disjunctiveFacets[position], result.facets_stats, dfacet);\n\n if (state.disjunctiveFacetsRefinements[dfacet]) {\n state.disjunctiveFacetsRefinements[dfacet].forEach(function(refinementValue) {\n // add the disjunctive refinements if it is no more retrieved\n if (!self.disjunctiveFacets[position].data[refinementValue] &&\n state.disjunctiveFacetsRefinements[dfacet].indexOf(refinementValue) > -1) {\n self.disjunctiveFacets[position].data[refinementValue] = 0;\n }\n });\n }\n }\n });\n nextDisjunctiveResult++;\n });\n\n // if we have some root level values for hierarchical facets, merge them\n state.getRefinedHierarchicalFacets().forEach(function(refinedFacet) {\n var hierarchicalFacet = state.getHierarchicalFacetByName(refinedFacet);\n var separator = state._getHierarchicalFacetSeparator(hierarchicalFacet);\n\n var currentRefinement = state.getHierarchicalRefinement(refinedFacet);\n // if we are already at a root refinement (or no refinement at all), there is no\n // root level values request\n if (currentRefinement.length === 0 || currentRefinement[0].split(separator).length < 2) {\n return;\n }\n\n var result = results[nextDisjunctiveResult];\n var facets = result && result.facets\n ? result.facets\n : {};\n Object.keys(facets).forEach(function(dfacet) {\n var facetResults = facets[dfacet];\n var position = findIndex(state.hierarchicalFacets, function(f) {\n return f.name === hierarchicalFacet.name;\n });\n var attributeIndex = findIndex(self.hierarchicalFacets[position], function(f) {\n return f.attribute === dfacet;\n });\n\n // previous refinements and no results so not able to find it\n if (attributeIndex === -1) {\n return;\n }\n\n // when we always get root levels, if the hits refinement is `beers > IPA` (count: 5),\n // then the disjunctive values will be `beers` (count: 100),\n // but we do not want to display\n // | beers (100)\n // > IPA (5)\n // We want\n // | beers (5)\n // > IPA (5)\n var defaultData = {};\n\n if (currentRefinement.length > 0) {\n var root = currentRefinement[0].split(separator)[0];\n defaultData[root] = self.hierarchicalFacets[position][attributeIndex].data[root];\n }\n\n self.hierarchicalFacets[position][attributeIndex].data = defaultsPure(\n defaultData,\n facetResults,\n self.hierarchicalFacets[position][attributeIndex].data\n );\n });\n\n nextDisjunctiveResult++;\n });\n\n // add the excludes\n Object.keys(state.facetsExcludes).forEach(function(facetName) {\n var excludes = state.facetsExcludes[facetName];\n var position = facetsIndices[facetName];\n\n self.facets[position] = {\n name: facetName,\n data: mainSubResponse.facets[facetName],\n exhaustive: mainSubResponse.exhaustiveFacetsCount\n };\n excludes.forEach(function(facetValue) {\n self.facets[position] = self.facets[position] || {name: facetName};\n self.facets[position].data = self.facets[position].data || {};\n self.facets[position].data[facetValue] = 0;\n });\n });\n\n /**\n * @type {Array}\n */\n this.hierarchicalFacets = this.hierarchicalFacets.map(generateHierarchicalTree(state));\n\n /**\n * @type {Array}\n */\n this.facets = compact(this.facets);\n /**\n * @type {Array}\n */\n this.disjunctiveFacets = compact(this.disjunctiveFacets);\n\n this._state = state;\n}\n\n/**\n * Get a facet object with its name\n * @deprecated\n * @param {string} name name of the faceted attribute\n * @return {SearchResults.Facet} the facet object\n */\nSearchResults.prototype.getFacetByName = function(name) {\n function predicate(facet) {\n return facet.name === name;\n }\n\n return find(this.facets, predicate) ||\n find(this.disjunctiveFacets, predicate) ||\n find(this.hierarchicalFacets, predicate);\n};\n\n/**\n * Get the facet values of a specified attribute from a SearchResults object.\n * @private\n * @param {SearchResults} results the search results to search in\n * @param {string} attribute name of the faceted attribute to search for\n * @return {array|object} facet values. For the hierarchical facets it is an object.\n */\nfunction extractNormalizedFacetValues(results, attribute) {\n function predicate(facet) {\n return facet.name === attribute;\n }\n\n if (results._state.isConjunctiveFacet(attribute)) {\n var facet = find(results.facets, predicate);\n if (!facet) return [];\n\n return Object.keys(facet.data).map(function(name) {\n return {\n name: name,\n count: facet.data[name],\n isRefined: results._state.isFacetRefined(attribute, name),\n isExcluded: results._state.isExcludeRefined(attribute, name)\n };\n });\n } else if (results._state.isDisjunctiveFacet(attribute)) {\n var disjunctiveFacet = find(results.disjunctiveFacets, predicate);\n if (!disjunctiveFacet) return [];\n\n return Object.keys(disjunctiveFacet.data).map(function(name) {\n return {\n name: name,\n count: disjunctiveFacet.data[name],\n isRefined: results._state.isDisjunctiveFacetRefined(attribute, name)\n };\n });\n } else if (results._state.isHierarchicalFacet(attribute)) {\n return find(results.hierarchicalFacets, predicate);\n }\n}\n\n/**\n * Sort nodes of a hierarchical facet results\n * @private\n * @param {HierarchicalFacet} node node to upon which we want to apply the sort\n */\nfunction recSort(sortFn, node) {\n if (!node.data || node.data.length === 0) {\n return node;\n }\n\n var children = node.data.map(function(childNode) {\n return recSort(sortFn, childNode);\n });\n var sortedChildren = sortFn(children);\n var newNode = merge({}, node, {data: sortedChildren});\n return newNode;\n}\n\nSearchResults.DEFAULT_SORT = ['isRefined:desc', 'count:desc', 'name:asc'];\n\nfunction vanillaSortFn(order, data) {\n return data.sort(order);\n}\n\n/**\n * Get a the list of values for a given facet attribute. Those values are sorted\n * refinement first, descending count (bigger value on top), and name ascending\n * (alphabetical order). The sort formula can overridden using either string based\n * predicates or a function.\n *\n * This method will return all the values returned by the Algolia engine plus all\n * the values already refined. This means that it can happen that the\n * `maxValuesPerFacet` [configuration](https://www.algolia.com/doc/rest-api/search#param-maxValuesPerFacet)\n * might not be respected if you have facet values that are already refined.\n * @param {string} attribute attribute name\n * @param {object} opts configuration options.\n * @param {Array. | function} opts.sortBy\n * When using strings, it consists of\n * the name of the [FacetValue](#SearchResults.FacetValue) or the\n * [HierarchicalFacet](#SearchResults.HierarchicalFacet) attributes with the\n * order (`asc` or `desc`). For example to order the value by count, the\n * argument would be `['count:asc']`.\n *\n * If only the attribute name is specified, the ordering defaults to the one\n * specified in the default value for this attribute.\n *\n * When not specified, the order is\n * ascending. This parameter can also be a function which takes two facet\n * values and should return a number, 0 if equal, 1 if the first argument is\n * bigger or -1 otherwise.\n *\n * The default value for this attribute `['isRefined:desc', 'count:desc', 'name:asc']`\n * @return {FacetValue[]|HierarchicalFacet|undefined} depending on the type of facet of\n * the attribute requested (hierarchical, disjunctive or conjunctive)\n * @example\n * helper.on('result', function(event){\n * //get values ordered only by name ascending using the string predicate\n * event.results.getFacetValues('city', {sortBy: ['name:asc']});\n * //get values ordered only by count ascending using a function\n * event.results.getFacetValues('city', {\n * // this is equivalent to ['count:asc']\n * sortBy: function(a, b) {\n * if (a.count === b.count) return 0;\n * if (a.count > b.count) return 1;\n * if (b.count > a.count) return -1;\n * }\n * });\n * });\n */\nSearchResults.prototype.getFacetValues = function(attribute, opts) {\n var facetValues = extractNormalizedFacetValues(this, attribute);\n if (!facetValues) {\n return undefined;\n }\n\n var options = defaultsPure({}, opts, {sortBy: SearchResults.DEFAULT_SORT});\n\n if (Array.isArray(options.sortBy)) {\n var order = formatSort(options.sortBy, SearchResults.DEFAULT_SORT);\n if (Array.isArray(facetValues)) {\n return orderBy(facetValues, order[0], order[1]);\n }\n // If facetValues is not an array, it's an object thus a hierarchical facet object\n return recSort(function(hierarchicalFacetValues) {\n return orderBy(hierarchicalFacetValues, order[0], order[1]);\n }, facetValues);\n } else if (typeof options.sortBy === 'function') {\n if (Array.isArray(facetValues)) {\n return facetValues.sort(options.sortBy);\n }\n // If facetValues is not an array, it's an object thus a hierarchical facet object\n return recSort(function(data) {\n return vanillaSortFn(options.sortBy, data);\n }, facetValues);\n }\n throw new Error(\n 'options.sortBy is optional but if defined it must be ' +\n 'either an array of string (predicates) or a sorting function'\n );\n};\n\n/**\n * Returns the facet stats if attribute is defined and the facet contains some.\n * Otherwise returns undefined.\n * @param {string} attribute name of the faceted attribute\n * @return {object} The stats of the facet\n */\nSearchResults.prototype.getFacetStats = function(attribute) {\n if (this._state.isConjunctiveFacet(attribute)) {\n return getFacetStatsIfAvailable(this.facets, attribute);\n } else if (this._state.isDisjunctiveFacet(attribute)) {\n return getFacetStatsIfAvailable(this.disjunctiveFacets, attribute);\n }\n\n return undefined;\n};\n\n/**\n * @typedef {Object} FacetListItem\n * @property {string} name\n */\n\n/**\n * @param {FacetListItem[]} facetList (has more items, but enough for here)\n * @param {string} facetName\n */\nfunction getFacetStatsIfAvailable(facetList, facetName) {\n var data = find(facetList, function(facet) {\n return facet.name === facetName;\n });\n return data && data.stats;\n}\n\n/**\n * Returns all refinements for all filters + tags. It also provides\n * additional information: count and exhaustiveness for each filter.\n *\n * See the [refinement type](#Refinement) for an exhaustive view of the available\n * data.\n *\n * Note that for a numeric refinement, results are grouped per operator, this\n * means that it will return responses for operators which are empty.\n *\n * @return {Array.} all the refinements\n */\nSearchResults.prototype.getRefinements = function() {\n var state = this._state;\n var results = this;\n var res = [];\n\n Object.keys(state.facetsRefinements).forEach(function(attributeName) {\n state.facetsRefinements[attributeName].forEach(function(name) {\n res.push(getRefinement(state, 'facet', attributeName, name, results.facets));\n });\n });\n\n Object.keys(state.facetsExcludes).forEach(function(attributeName) {\n state.facetsExcludes[attributeName].forEach(function(name) {\n res.push(getRefinement(state, 'exclude', attributeName, name, results.facets));\n });\n });\n\n Object.keys(state.disjunctiveFacetsRefinements).forEach(function(attributeName) {\n state.disjunctiveFacetsRefinements[attributeName].forEach(function(name) {\n res.push(getRefinement(state, 'disjunctive', attributeName, name, results.disjunctiveFacets));\n });\n });\n\n Object.keys(state.hierarchicalFacetsRefinements).forEach(function(attributeName) {\n state.hierarchicalFacetsRefinements[attributeName].forEach(function(name) {\n res.push(getHierarchicalRefinement(state, attributeName, name, results.hierarchicalFacets));\n });\n });\n\n\n Object.keys(state.numericRefinements).forEach(function(attributeName) {\n var operators = state.numericRefinements[attributeName];\n Object.keys(operators).forEach(function(operator) {\n operators[operator].forEach(function(value) {\n res.push({\n type: 'numeric',\n attributeName: attributeName,\n name: value,\n numericValue: value,\n operator: operator\n });\n });\n });\n });\n\n state.tagRefinements.forEach(function(name) {\n res.push({type: 'tag', attributeName: '_tags', name: name});\n });\n\n return res;\n};\n\n/**\n * @typedef {Object} Facet\n * @property {string} name\n * @property {Object} data\n * @property {boolean} exhaustive\n */\n\n/**\n * @param {*} state\n * @param {*} type\n * @param {string} attributeName\n * @param {*} name\n * @param {Facet[]} resultsFacets\n */\nfunction getRefinement(state, type, attributeName, name, resultsFacets) {\n var facet = find(resultsFacets, function(f) {\n return f.name === attributeName;\n });\n var count = facet && facet.data && facet.data[name] ? facet.data[name] : 0;\n var exhaustive = (facet && facet.exhaustive) || false;\n\n return {\n type: type,\n attributeName: attributeName,\n name: name,\n count: count,\n exhaustive: exhaustive\n };\n}\n\n/**\n * @param {*} state\n * @param {string} attributeName\n * @param {*} name\n * @param {Facet[]} resultsFacets\n */\nfunction getHierarchicalRefinement(state, attributeName, name, resultsFacets) {\n var facetDeclaration = state.getHierarchicalFacetByName(attributeName);\n var separator = state._getHierarchicalFacetSeparator(facetDeclaration);\n var split = name.split(separator);\n var rootFacet = find(resultsFacets, function(facet) {\n return facet.name === attributeName;\n });\n\n var facet = split.reduce(function(intermediateFacet, part) {\n var newFacet =\n intermediateFacet && find(intermediateFacet.data, function(f) {\n return f.name === part;\n });\n return newFacet !== undefined ? newFacet : intermediateFacet;\n }, rootFacet);\n\n var count = (facet && facet.count) || 0;\n var exhaustive = (facet && facet.exhaustive) || false;\n var path = (facet && facet.path) || '';\n\n return {\n type: 'hierarchical',\n attributeName: attributeName,\n name: path,\n count: count,\n exhaustive: exhaustive\n };\n}\n\nmodule.exports = SearchResults;\n","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nfunction EventEmitter() {\n this._events = this._events || {};\n this._maxListeners = this._maxListeners || undefined;\n}\nmodule.exports = EventEmitter;\n\n// Backwards-compat with node 0.10.x\nEventEmitter.EventEmitter = EventEmitter;\n\nEventEmitter.prototype._events = undefined;\nEventEmitter.prototype._maxListeners = undefined;\n\n// By default EventEmitters will print a warning if more than 10 listeners are\n// added to it. This is a useful default which helps finding memory leaks.\nEventEmitter.defaultMaxListeners = 10;\n\n// Obviously not all Emitters should be limited to 10. This function allows\n// that to be increased. Set to zero for unlimited.\nEventEmitter.prototype.setMaxListeners = function(n) {\n if (!isNumber(n) || n < 0 || isNaN(n))\n throw TypeError('n must be a positive number');\n this._maxListeners = n;\n return this;\n};\n\nEventEmitter.prototype.emit = function(type) {\n var er, handler, len, args, i, listeners;\n\n if (!this._events)\n this._events = {};\n\n // If there is no 'error' event listener then throw.\n if (type === 'error') {\n if (!this._events.error ||\n (isObject(this._events.error) && !this._events.error.length)) {\n er = arguments[1];\n if (er instanceof Error) {\n throw er; // Unhandled 'error' event\n } else {\n // At least give some kind of context to the user\n var err = new Error('Uncaught, unspecified \"error\" event. (' + er + ')');\n err.context = er;\n throw err;\n }\n }\n }\n\n handler = this._events[type];\n\n if (isUndefined(handler))\n return false;\n\n if (isFunction(handler)) {\n switch (arguments.length) {\n // fast cases\n case 1:\n handler.call(this);\n break;\n case 2:\n handler.call(this, arguments[1]);\n break;\n case 3:\n handler.call(this, arguments[1], arguments[2]);\n break;\n // slower\n default:\n args = Array.prototype.slice.call(arguments, 1);\n handler.apply(this, args);\n }\n } else if (isObject(handler)) {\n args = Array.prototype.slice.call(arguments, 1);\n listeners = handler.slice();\n len = listeners.length;\n for (i = 0; i < len; i++)\n listeners[i].apply(this, args);\n }\n\n return true;\n};\n\nEventEmitter.prototype.addListener = function(type, listener) {\n var m;\n\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n if (!this._events)\n this._events = {};\n\n // To avoid recursion in the case that type === \"newListener\"! Before\n // adding it to the listeners, first emit \"newListener\".\n if (this._events.newListener)\n this.emit('newListener', type,\n isFunction(listener.listener) ?\n listener.listener : listener);\n\n if (!this._events[type])\n // Optimize the case of one listener. Don't need the extra array object.\n this._events[type] = listener;\n else if (isObject(this._events[type]))\n // If we've already got an array, just append.\n this._events[type].push(listener);\n else\n // Adding the second element, need to change to array.\n this._events[type] = [this._events[type], listener];\n\n // Check for listener leak\n if (isObject(this._events[type]) && !this._events[type].warned) {\n if (!isUndefined(this._maxListeners)) {\n m = this._maxListeners;\n } else {\n m = EventEmitter.defaultMaxListeners;\n }\n\n if (m && m > 0 && this._events[type].length > m) {\n this._events[type].warned = true;\n console.error('(node) warning: possible EventEmitter memory ' +\n 'leak detected. %d listeners added. ' +\n 'Use emitter.setMaxListeners() to increase limit.',\n this._events[type].length);\n if (typeof console.trace === 'function') {\n // not supported in IE 10\n console.trace();\n }\n }\n }\n\n return this;\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener;\n\nEventEmitter.prototype.once = function(type, listener) {\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n var fired = false;\n\n function g() {\n this.removeListener(type, g);\n\n if (!fired) {\n fired = true;\n listener.apply(this, arguments);\n }\n }\n\n g.listener = listener;\n this.on(type, g);\n\n return this;\n};\n\n// emits a 'removeListener' event iff the listener was removed\nEventEmitter.prototype.removeListener = function(type, listener) {\n var list, position, length, i;\n\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n if (!this._events || !this._events[type])\n return this;\n\n list = this._events[type];\n length = list.length;\n position = -1;\n\n if (list === listener ||\n (isFunction(list.listener) && list.listener === listener)) {\n delete this._events[type];\n if (this._events.removeListener)\n this.emit('removeListener', type, listener);\n\n } else if (isObject(list)) {\n for (i = length; i-- > 0;) {\n if (list[i] === listener ||\n (list[i].listener && list[i].listener === listener)) {\n position = i;\n break;\n }\n }\n\n if (position < 0)\n return this;\n\n if (list.length === 1) {\n list.length = 0;\n delete this._events[type];\n } else {\n list.splice(position, 1);\n }\n\n if (this._events.removeListener)\n this.emit('removeListener', type, listener);\n }\n\n return this;\n};\n\nEventEmitter.prototype.removeAllListeners = function(type) {\n var key, listeners;\n\n if (!this._events)\n return this;\n\n // not listening for removeListener, no need to emit\n if (!this._events.removeListener) {\n if (arguments.length === 0)\n this._events = {};\n else if (this._events[type])\n delete this._events[type];\n return this;\n }\n\n // emit removeListener for all listeners on all events\n if (arguments.length === 0) {\n for (key in this._events) {\n if (key === 'removeListener') continue;\n this.removeAllListeners(key);\n }\n this.removeAllListeners('removeListener');\n this._events = {};\n return this;\n }\n\n listeners = this._events[type];\n\n if (isFunction(listeners)) {\n this.removeListener(type, listeners);\n } else if (listeners) {\n // LIFO order\n while (listeners.length)\n this.removeListener(type, listeners[listeners.length - 1]);\n }\n delete this._events[type];\n\n return this;\n};\n\nEventEmitter.prototype.listeners = function(type) {\n var ret;\n if (!this._events || !this._events[type])\n ret = [];\n else if (isFunction(this._events[type]))\n ret = [this._events[type]];\n else\n ret = this._events[type].slice();\n return ret;\n};\n\nEventEmitter.prototype.listenerCount = function(type) {\n if (this._events) {\n var evlistener = this._events[type];\n\n if (isFunction(evlistener))\n return 1;\n else if (evlistener)\n return evlistener.length;\n }\n return 0;\n};\n\nEventEmitter.listenerCount = function(emitter, type) {\n return emitter.listenerCount(type);\n};\n\nfunction isFunction(arg) {\n return typeof arg === 'function';\n}\n\nfunction isNumber(arg) {\n return typeof arg === 'number';\n}\n\nfunction isObject(arg) {\n return typeof arg === 'object' && arg !== null;\n}\n\nfunction isUndefined(arg) {\n return arg === void 0;\n}\n","'use strict';\n\nfunction inherits(ctor, superCtor) {\n ctor.prototype = Object.create(superCtor.prototype, {\n constructor: {\n value: ctor,\n enumerable: false,\n writable: true,\n configurable: true\n }\n });\n}\n\nmodule.exports = inherits;\n","'use strict';\n\nvar events = require('events');\nvar inherits = require('../functions/inherits');\n\n/**\n * A DerivedHelper is a way to create sub requests to\n * Algolia from a main helper.\n * @class\n * @classdesc The DerivedHelper provides an event based interface for search callbacks:\n * - search: when a search is triggered using the `search()` method.\n * - result: when the response is retrieved from Algolia and is processed.\n * This event contains a {@link SearchResults} object and the\n * {@link SearchParameters} corresponding to this answer.\n */\nfunction DerivedHelper(mainHelper, fn) {\n this.main = mainHelper;\n this.fn = fn;\n this.lastResults = null;\n}\n\ninherits(DerivedHelper, events.EventEmitter);\n\n/**\n * Detach this helper from the main helper\n * @return {undefined}\n * @throws Error if the derived helper is already detached\n */\nDerivedHelper.prototype.detach = function() {\n this.removeAllListeners();\n this.main.detachDerivedHelper(this);\n};\n\nDerivedHelper.prototype.getModifiedState = function(parameters) {\n return this.fn(parameters);\n};\n\nmodule.exports = DerivedHelper;\n","'use strict';\n\nvar merge = require('./functions/merge');\n\nvar requestBuilder = {\n /**\n * Get all the queries to send to the client, those queries can used directly\n * with the Algolia client.\n * @private\n * @return {object[]} The queries\n */\n _getQueries: function getQueries(index, state) {\n var queries = [];\n\n // One query for the hits\n queries.push({\n indexName: index,\n params: requestBuilder._getHitsSearchParams(state)\n });\n\n // One for each disjunctive facets\n state.getRefinedDisjunctiveFacets().forEach(function(refinedFacet) {\n queries.push({\n indexName: index,\n params: requestBuilder._getDisjunctiveFacetSearchParams(state, refinedFacet)\n });\n });\n\n // maybe more to get the root level of hierarchical facets when activated\n state.getRefinedHierarchicalFacets().forEach(function(refinedFacet) {\n var hierarchicalFacet = state.getHierarchicalFacetByName(refinedFacet);\n\n var currentRefinement = state.getHierarchicalRefinement(refinedFacet);\n // if we are deeper than level 0 (starting from `beer > IPA`)\n // we want to get the root values\n var separator = state._getHierarchicalFacetSeparator(hierarchicalFacet);\n if (currentRefinement.length > 0 && currentRefinement[0].split(separator).length > 1) {\n queries.push({\n indexName: index,\n params: requestBuilder._getDisjunctiveFacetSearchParams(state, refinedFacet, true)\n });\n }\n });\n\n return queries;\n },\n\n /**\n * Build search parameters used to fetch hits\n * @private\n * @return {object.}\n */\n _getHitsSearchParams: function(state) {\n var facets = state.facets\n .concat(state.disjunctiveFacets)\n .concat(requestBuilder._getHitsHierarchicalFacetsAttributes(state));\n\n\n var facetFilters = requestBuilder._getFacetFilters(state);\n var numericFilters = requestBuilder._getNumericFilters(state);\n var tagFilters = requestBuilder._getTagFilters(state);\n var additionalParams = {\n facets: facets,\n tagFilters: tagFilters\n };\n\n if (facetFilters.length > 0) {\n additionalParams.facetFilters = facetFilters;\n }\n\n if (numericFilters.length > 0) {\n additionalParams.numericFilters = numericFilters;\n }\n\n return merge({}, state.getQueryParams(), additionalParams);\n },\n\n /**\n * Build search parameters used to fetch a disjunctive facet\n * @private\n * @param {string} facet the associated facet name\n * @param {boolean} hierarchicalRootLevel ?? FIXME\n * @return {object}\n */\n _getDisjunctiveFacetSearchParams: function(state, facet, hierarchicalRootLevel) {\n var facetFilters = requestBuilder._getFacetFilters(state, facet, hierarchicalRootLevel);\n var numericFilters = requestBuilder._getNumericFilters(state, facet);\n var tagFilters = requestBuilder._getTagFilters(state);\n var additionalParams = {\n hitsPerPage: 1,\n page: 0,\n attributesToRetrieve: [],\n attributesToHighlight: [],\n attributesToSnippet: [],\n tagFilters: tagFilters,\n analytics: false,\n clickAnalytics: false\n };\n\n var hierarchicalFacet = state.getHierarchicalFacetByName(facet);\n\n if (hierarchicalFacet) {\n additionalParams.facets = requestBuilder._getDisjunctiveHierarchicalFacetAttribute(\n state,\n hierarchicalFacet,\n hierarchicalRootLevel\n );\n } else {\n additionalParams.facets = facet;\n }\n\n if (numericFilters.length > 0) {\n additionalParams.numericFilters = numericFilters;\n }\n\n if (facetFilters.length > 0) {\n additionalParams.facetFilters = facetFilters;\n }\n\n return merge({}, state.getQueryParams(), additionalParams);\n },\n\n /**\n * Return the numeric filters in an algolia request fashion\n * @private\n * @param {string} [facetName] the name of the attribute for which the filters should be excluded\n * @return {string[]} the numeric filters in the algolia format\n */\n _getNumericFilters: function(state, facetName) {\n if (state.numericFilters) {\n return state.numericFilters;\n }\n\n var numericFilters = [];\n\n Object.keys(state.numericRefinements).forEach(function(attribute) {\n var operators = state.numericRefinements[attribute] || {};\n Object.keys(operators).forEach(function(operator) {\n var values = operators[operator] || [];\n if (facetName !== attribute) {\n values.forEach(function(value) {\n if (Array.isArray(value)) {\n var vs = value.map(function(v) {\n return attribute + operator + v;\n });\n numericFilters.push(vs);\n } else {\n numericFilters.push(attribute + operator + value);\n }\n });\n }\n });\n });\n\n return numericFilters;\n },\n\n /**\n * Return the tags filters depending\n * @private\n * @return {string}\n */\n _getTagFilters: function(state) {\n if (state.tagFilters) {\n return state.tagFilters;\n }\n\n return state.tagRefinements.join(',');\n },\n\n\n /**\n * Build facetFilters parameter based on current refinements. The array returned\n * contains strings representing the facet filters in the algolia format.\n * @private\n * @param {string} [facet] if set, the current disjunctive facet\n * @return {array.}\n */\n _getFacetFilters: function(state, facet, hierarchicalRootLevel) {\n var facetFilters = [];\n\n var facetsRefinements = state.facetsRefinements || {};\n Object.keys(facetsRefinements).forEach(function(facetName) {\n var facetValues = facetsRefinements[facetName] || [];\n facetValues.forEach(function(facetValue) {\n facetFilters.push(facetName + ':' + facetValue);\n });\n });\n\n var facetsExcludes = state.facetsExcludes || {};\n Object.keys(facetsExcludes).forEach(function(facetName) {\n var facetValues = facetsExcludes[facetName] || [];\n facetValues.forEach(function(facetValue) {\n facetFilters.push(facetName + ':-' + facetValue);\n });\n });\n\n var disjunctiveFacetsRefinements = state.disjunctiveFacetsRefinements || {};\n Object.keys(disjunctiveFacetsRefinements).forEach(function(facetName) {\n var facetValues = disjunctiveFacetsRefinements[facetName] || [];\n if (facetName === facet || !facetValues || facetValues.length === 0) {\n return;\n }\n var orFilters = [];\n\n facetValues.forEach(function(facetValue) {\n orFilters.push(facetName + ':' + facetValue);\n });\n\n facetFilters.push(orFilters);\n });\n\n var hierarchicalFacetsRefinements = state.hierarchicalFacetsRefinements || {};\n Object.keys(hierarchicalFacetsRefinements).forEach(function(facetName) {\n var facetValues = hierarchicalFacetsRefinements[facetName] || [];\n var facetValue = facetValues[0];\n\n if (facetValue === undefined) {\n return;\n }\n\n var hierarchicalFacet = state.getHierarchicalFacetByName(facetName);\n var separator = state._getHierarchicalFacetSeparator(hierarchicalFacet);\n var rootPath = state._getHierarchicalRootPath(hierarchicalFacet);\n var attributeToRefine;\n var attributesIndex;\n\n // we ask for parent facet values only when the `facet` is the current hierarchical facet\n if (facet === facetName) {\n // if we are at the root level already, no need to ask for facet values, we get them from\n // the hits query\n if (facetValue.indexOf(separator) === -1 || (!rootPath && hierarchicalRootLevel === true) ||\n (rootPath && rootPath.split(separator).length === facetValue.split(separator).length)) {\n return;\n }\n\n if (!rootPath) {\n attributesIndex = facetValue.split(separator).length - 2;\n facetValue = facetValue.slice(0, facetValue.lastIndexOf(separator));\n } else {\n attributesIndex = rootPath.split(separator).length - 1;\n facetValue = rootPath;\n }\n\n attributeToRefine = hierarchicalFacet.attributes[attributesIndex];\n } else {\n attributesIndex = facetValue.split(separator).length - 1;\n\n attributeToRefine = hierarchicalFacet.attributes[attributesIndex];\n }\n\n if (attributeToRefine) {\n facetFilters.push([attributeToRefine + ':' + facetValue]);\n }\n });\n\n return facetFilters;\n },\n\n _getHitsHierarchicalFacetsAttributes: function(state) {\n var out = [];\n\n return state.hierarchicalFacets.reduce(\n // ask for as much levels as there's hierarchical refinements\n function getHitsAttributesForHierarchicalFacet(allAttributes, hierarchicalFacet) {\n var hierarchicalRefinement = state.getHierarchicalRefinement(hierarchicalFacet.name)[0];\n\n // if no refinement, ask for root level\n if (!hierarchicalRefinement) {\n allAttributes.push(hierarchicalFacet.attributes[0]);\n return allAttributes;\n }\n\n var separator = state._getHierarchicalFacetSeparator(hierarchicalFacet);\n var level = hierarchicalRefinement.split(separator).length;\n var newAttributes = hierarchicalFacet.attributes.slice(0, level + 1);\n\n return allAttributes.concat(newAttributes);\n }, out);\n },\n\n _getDisjunctiveHierarchicalFacetAttribute: function(state, hierarchicalFacet, rootLevel) {\n var separator = state._getHierarchicalFacetSeparator(hierarchicalFacet);\n if (rootLevel === true) {\n var rootPath = state._getHierarchicalRootPath(hierarchicalFacet);\n var attributeIndex = 0;\n\n if (rootPath) {\n attributeIndex = rootPath.split(separator).length;\n }\n return [hierarchicalFacet.attributes[attributeIndex]];\n }\n\n var hierarchicalRefinement = state.getHierarchicalRefinement(hierarchicalFacet.name)[0] || '';\n // if refinement is 'beers > IPA > Flying dog',\n // then we want `facets: ['beers > IPA']` as disjunctive facet (parent level values)\n\n var parentLevel = hierarchicalRefinement.split(separator).length - 1;\n return hierarchicalFacet.attributes.slice(0, parentLevel + 1);\n },\n\n getSearchForFacetQuery: function(facetName, query, maxFacetHits, state) {\n var stateForSearchForFacetValues = state.isDisjunctiveFacet(facetName) ?\n state.clearRefinements(facetName) :\n state;\n var searchForFacetSearchParameters = {\n facetQuery: query,\n facetName: facetName\n };\n if (typeof maxFacetHits === 'number') {\n searchForFacetSearchParameters.maxFacetHits = maxFacetHits;\n }\n return merge(\n {},\n requestBuilder._getHitsSearchParams(stateForSearchForFacetValues),\n searchForFacetSearchParameters\n );\n }\n};\n\nmodule.exports = requestBuilder;\n","'use strict';\n\nmodule.exports = '3.3.4';\n","'use strict';\n\nvar SearchParameters = require('./SearchParameters');\nvar SearchResults = require('./SearchResults');\nvar DerivedHelper = require('./DerivedHelper');\nvar requestBuilder = require('./requestBuilder');\n\nvar events = require('events');\nvar inherits = require('./functions/inherits');\nvar objectHasKeys = require('./functions/objectHasKeys');\n\nvar version = require('./version');\n\n/**\n * Event triggered when a parameter is set or updated\n * @event AlgoliaSearchHelper#event:change\n * @property {object} event\n * @property {SearchParameters} event.state the current parameters with the latest changes applied\n * @property {SearchResults} event.results the previous results received from Algolia. `null` before the first request\n * @example\n * helper.on('change', function(event) {\n * console.log('The parameters have changed');\n * });\n */\n\n/**\n * Event triggered when a main search is sent to Algolia\n * @event AlgoliaSearchHelper#event:search\n * @property {object} event\n * @property {SearchParameters} event.state the parameters used for this search\n * @property {SearchResults} event.results the results from the previous search. `null` if it is the first search.\n * @example\n * helper.on('search', function(event) {\n * console.log('Search sent');\n * });\n */\n\n/**\n * Event triggered when a search using `searchForFacetValues` is sent to Algolia\n * @event AlgoliaSearchHelper#event:searchForFacetValues\n * @property {object} event\n * @property {SearchParameters} event.state the parameters used for this search it is the first search.\n * @property {string} event.facet the facet searched into\n * @property {string} event.query the query used to search in the facets\n * @example\n * helper.on('searchForFacetValues', function(event) {\n * console.log('searchForFacetValues sent');\n * });\n */\n\n/**\n * Event triggered when a search using `searchOnce` is sent to Algolia\n * @event AlgoliaSearchHelper#event:searchOnce\n * @property {object} event\n * @property {SearchParameters} event.state the parameters used for this search it is the first search.\n * @example\n * helper.on('searchOnce', function(event) {\n * console.log('searchOnce sent');\n * });\n */\n\n/**\n * Event triggered when the results are retrieved from Algolia\n * @event AlgoliaSearchHelper#event:result\n * @property {object} event\n * @property {SearchResults} event.results the results received from Algolia\n * @property {SearchParameters} event.state the parameters used to query Algolia. Those might be different from the one in the helper instance (for example if the network is unreliable).\n * @example\n * helper.on('result', function(event) {\n * console.log('Search results received');\n * });\n */\n\n/**\n * Event triggered when Algolia sends back an error. For example, if an unknown parameter is\n * used, the error can be caught using this event.\n * @event AlgoliaSearchHelper#event:error\n * @property {object} event\n * @property {Error} event.error the error returned by the Algolia.\n * @example\n * helper.on('error', function(event) {\n * console.log('Houston we got a problem.');\n * });\n */\n\n/**\n * Event triggered when the queue of queries have been depleted (with any result or outdated queries)\n * @event AlgoliaSearchHelper#event:searchQueueEmpty\n * @example\n * helper.on('searchQueueEmpty', function() {\n * console.log('No more search pending');\n * // This is received before the result event if we're not expecting new results\n * });\n *\n * helper.search();\n */\n\n/**\n * Initialize a new AlgoliaSearchHelper\n * @class\n * @classdesc The AlgoliaSearchHelper is a class that ease the management of the\n * search. It provides an event based interface for search callbacks:\n * - change: when the internal search state is changed.\n * This event contains a {@link SearchParameters} object and the\n * {@link SearchResults} of the last result if any.\n * - search: when a search is triggered using the `search()` method.\n * - result: when the response is retrieved from Algolia and is processed.\n * This event contains a {@link SearchResults} object and the\n * {@link SearchParameters} corresponding to this answer.\n * - error: when the response is an error. This event contains the error returned by the server.\n * @param {AlgoliaSearch} client an AlgoliaSearch client\n * @param {string} index the index name to query\n * @param {SearchParameters | object} options an object defining the initial\n * config of the search. It doesn't have to be a {SearchParameters},\n * just an object containing the properties you need from it.\n */\nfunction AlgoliaSearchHelper(client, index, options) {\n if (typeof client.addAlgoliaAgent === 'function') {\n client.addAlgoliaAgent('JS Helper (' + version + ')');\n }\n\n this.setClient(client);\n var opts = options || {};\n opts.index = index;\n this.state = SearchParameters.make(opts);\n this.lastResults = null;\n this._queryId = 0;\n this._lastQueryIdReceived = -1;\n this.derivedHelpers = [];\n this._currentNbQueries = 0;\n}\n\ninherits(AlgoliaSearchHelper, events.EventEmitter);\n\n/**\n * Start the search with the parameters set in the state. When the\n * method is called, it triggers a `search` event. The results will\n * be available through the `result` event. If an error occurs, an\n * `error` will be fired instead.\n * @return {AlgoliaSearchHelper}\n * @fires search\n * @fires result\n * @fires error\n * @chainable\n */\nAlgoliaSearchHelper.prototype.search = function() {\n this._search({onlyWithDerivedHelpers: false});\n return this;\n};\n\nAlgoliaSearchHelper.prototype.searchOnlyWithDerivedHelpers = function() {\n this._search({onlyWithDerivedHelpers: true});\n return this;\n};\n\n/**\n * Gets the search query parameters that would be sent to the Algolia Client\n * for the hits\n * @return {object} Query Parameters\n */\nAlgoliaSearchHelper.prototype.getQuery = function() {\n var state = this.state;\n return requestBuilder._getHitsSearchParams(state);\n};\n\n/**\n * Start a search using a modified version of the current state. This method does\n * not trigger the helper lifecycle and does not modify the state kept internally\n * by the helper. This second aspect means that the next search call will be the\n * same as a search call before calling searchOnce.\n * @param {object} options can contain all the parameters that can be set to SearchParameters\n * plus the index\n * @param {function} [callback] optional callback executed when the response from the\n * server is back.\n * @return {promise|undefined} if a callback is passed the method returns undefined\n * otherwise it returns a promise containing an object with two keys :\n * - content with a SearchResults\n * - state with the state used for the query as a SearchParameters\n * @example\n * // Changing the number of records returned per page to 1\n * // This example uses the callback API\n * var state = helper.searchOnce({hitsPerPage: 1},\n * function(error, content, state) {\n * // if an error occurred it will be passed in error, otherwise its value is null\n * // content contains the results formatted as a SearchResults\n * // state is the instance of SearchParameters used for this search\n * });\n * @example\n * // Changing the number of records returned per page to 1\n * // This example uses the promise API\n * var state1 = helper.searchOnce({hitsPerPage: 1})\n * .then(promiseHandler);\n *\n * function promiseHandler(res) {\n * // res contains\n * // {\n * // content : SearchResults\n * // state : SearchParameters (the one used for this specific search)\n * // }\n * }\n */\nAlgoliaSearchHelper.prototype.searchOnce = function(options, cb) {\n var tempState = !options ? this.state : this.state.setQueryParameters(options);\n var queries = requestBuilder._getQueries(tempState.index, tempState);\n var self = this;\n\n this._currentNbQueries++;\n\n this.emit('searchOnce', {\n state: tempState\n });\n\n if (cb) {\n this.client\n .search(queries)\n .then(function(content) {\n self._currentNbQueries--;\n if (self._currentNbQueries === 0) {\n self.emit('searchQueueEmpty');\n }\n\n cb(null, new SearchResults(tempState, content.results), tempState);\n })\n .catch(function(err) {\n self._currentNbQueries--;\n if (self._currentNbQueries === 0) {\n self.emit('searchQueueEmpty');\n }\n\n cb(err, null, tempState);\n });\n\n return undefined;\n }\n\n return this.client.search(queries).then(function(content) {\n self._currentNbQueries--;\n if (self._currentNbQueries === 0) self.emit('searchQueueEmpty');\n return {\n content: new SearchResults(tempState, content.results),\n state: tempState,\n _originalResponse: content\n };\n }, function(e) {\n self._currentNbQueries--;\n if (self._currentNbQueries === 0) self.emit('searchQueueEmpty');\n throw e;\n });\n};\n\n/**\n * Structure of each result when using\n * [`searchForFacetValues()`](reference.html#AlgoliaSearchHelper#searchForFacetValues)\n * @typedef FacetSearchHit\n * @type {object}\n * @property {string} value the facet value\n * @property {string} highlighted the facet value highlighted with the query string\n * @property {number} count number of occurrence of this facet value\n * @property {boolean} isRefined true if the value is already refined\n */\n\n/**\n * Structure of the data resolved by the\n * [`searchForFacetValues()`](reference.html#AlgoliaSearchHelper#searchForFacetValues)\n * promise.\n * @typedef FacetSearchResult\n * @type {object}\n * @property {FacetSearchHit} facetHits the results for this search for facet values\n * @property {number} processingTimeMS time taken by the query inside the engine\n */\n\n/**\n * Search for facet values based on an query and the name of a faceted attribute. This\n * triggers a search and will return a promise. On top of using the query, it also sends\n * the parameters from the state so that the search is narrowed down to only the possible values.\n *\n * See the description of [FacetSearchResult](reference.html#FacetSearchResult)\n * @param {string} facet the name of the faceted attribute\n * @param {string} query the string query for the search\n * @param {number} [maxFacetHits] the maximum number values returned. Should be > 0 and <= 100\n * @param {object} [userState] the set of custom parameters to use on top of the current state. Setting a property to `undefined` removes\n * it in the generated query.\n * @return {promise.} the results of the search\n */\nAlgoliaSearchHelper.prototype.searchForFacetValues = function(facet, query, maxFacetHits, userState) {\n var clientHasSFFV = typeof this.client.searchForFacetValues === 'function';\n if (\n !clientHasSFFV &&\n typeof this.client.initIndex !== 'function'\n ) {\n throw new Error(\n 'search for facet values (searchable) was called, but this client does not have a function client.searchForFacetValues or client.initIndex(index).searchForFacetValues'\n );\n }\n var state = this.state.setQueryParameters(userState || {});\n var isDisjunctive = state.isDisjunctiveFacet(facet);\n var algoliaQuery = requestBuilder.getSearchForFacetQuery(facet, query, maxFacetHits, state);\n\n this._currentNbQueries++;\n var self = this;\n\n this.emit('searchForFacetValues', {\n state: state,\n facet: facet,\n query: query\n });\n\n var searchForFacetValuesPromise = clientHasSFFV\n ? this.client.searchForFacetValues([{indexName: state.index, params: algoliaQuery}])\n : this.client.initIndex(state.index).searchForFacetValues(algoliaQuery);\n\n return searchForFacetValuesPromise.then(function addIsRefined(content) {\n self._currentNbQueries--;\n if (self._currentNbQueries === 0) self.emit('searchQueueEmpty');\n\n content = Array.isArray(content) ? content[0] : content;\n\n content.facetHits.forEach(function(f) {\n f.isRefined = isDisjunctive\n ? state.isDisjunctiveFacetRefined(facet, f.value)\n : state.isFacetRefined(facet, f.value);\n });\n\n return content;\n }, function(e) {\n self._currentNbQueries--;\n if (self._currentNbQueries === 0) self.emit('searchQueueEmpty');\n throw e;\n });\n};\n\n/**\n * Sets the text query used for the search.\n *\n * This method resets the current page to 0.\n * @param {string} q the user query\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.setQuery = function(q) {\n this._change({\n state: this.state.resetPage().setQuery(q),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Remove all the types of refinements except tags. A string can be provided to remove\n * only the refinements of a specific attribute. For more advanced use case, you can\n * provide a function instead. This function should follow the\n * [clearCallback definition](#SearchParameters.clearCallback).\n *\n * This method resets the current page to 0.\n * @param {string} [name] optional name of the facet / attribute on which we want to remove all refinements\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n * @example\n * // Removing all the refinements\n * helper.clearRefinements().search();\n * @example\n * // Removing all the filters on a the category attribute.\n * helper.clearRefinements('category').search();\n * @example\n * // Removing only the exclude filters on the category facet.\n * helper.clearRefinements(function(value, attribute, type) {\n * return type === 'exclude' && attribute === 'category';\n * }).search();\n */\nAlgoliaSearchHelper.prototype.clearRefinements = function(name) {\n this._change({\n state: this.state.resetPage().clearRefinements(name),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Remove all the tag filters.\n *\n * This method resets the current page to 0.\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.clearTags = function() {\n this._change({\n state: this.state.resetPage().clearTags(),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Adds a disjunctive filter to a faceted attribute with the `value` provided. If the\n * filter is already set, it doesn't change the filters.\n *\n * This method resets the current page to 0.\n * @param {string} facet the facet to refine\n * @param {string} value the associated value (will be converted to string)\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.addDisjunctiveFacetRefinement = function(facet, value) {\n this._change({\n state: this.state.resetPage().addDisjunctiveFacetRefinement(facet, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * @deprecated since version 2.4.0, see {@link AlgoliaSearchHelper#addDisjunctiveFacetRefinement}\n */\nAlgoliaSearchHelper.prototype.addDisjunctiveRefine = function() {\n return this.addDisjunctiveFacetRefinement.apply(this, arguments);\n};\n\n/**\n * Adds a refinement on a hierarchical facet. It will throw\n * an exception if the facet is not defined or if the facet\n * is already refined.\n *\n * This method resets the current page to 0.\n * @param {string} facet the facet name\n * @param {string} path the hierarchical facet path\n * @return {AlgoliaSearchHelper}\n * @throws Error if the facet is not defined or if the facet is refined\n * @chainable\n * @fires change\n */\nAlgoliaSearchHelper.prototype.addHierarchicalFacetRefinement = function(facet, value) {\n this._change({\n state: this.state.resetPage().addHierarchicalFacetRefinement(facet, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Adds a an numeric filter to an attribute with the `operator` and `value` provided. If the\n * filter is already set, it doesn't change the filters.\n *\n * This method resets the current page to 0.\n * @param {string} attribute the attribute on which the numeric filter applies\n * @param {string} operator the operator of the filter\n * @param {number} value the value of the filter\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.addNumericRefinement = function(attribute, operator, value) {\n this._change({\n state: this.state.resetPage().addNumericRefinement(attribute, operator, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Adds a filter to a faceted attribute with the `value` provided. If the\n * filter is already set, it doesn't change the filters.\n *\n * This method resets the current page to 0.\n * @param {string} facet the facet to refine\n * @param {string} value the associated value (will be converted to string)\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.addFacetRefinement = function(facet, value) {\n this._change({\n state: this.state.resetPage().addFacetRefinement(facet, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * @deprecated since version 2.4.0, see {@link AlgoliaSearchHelper#addFacetRefinement}\n */\nAlgoliaSearchHelper.prototype.addRefine = function() {\n return this.addFacetRefinement.apply(this, arguments);\n};\n\n\n/**\n * Adds a an exclusion filter to a faceted attribute with the `value` provided. If the\n * filter is already set, it doesn't change the filters.\n *\n * This method resets the current page to 0.\n * @param {string} facet the facet to refine\n * @param {string} value the associated value (will be converted to string)\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.addFacetExclusion = function(facet, value) {\n this._change({\n state: this.state.resetPage().addExcludeRefinement(facet, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * @deprecated since version 2.4.0, see {@link AlgoliaSearchHelper#addFacetExclusion}\n */\nAlgoliaSearchHelper.prototype.addExclude = function() {\n return this.addFacetExclusion.apply(this, arguments);\n};\n\n/**\n * Adds a tag filter with the `tag` provided. If the\n * filter is already set, it doesn't change the filters.\n *\n * This method resets the current page to 0.\n * @param {string} tag the tag to add to the filter\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.addTag = function(tag) {\n this._change({\n state: this.state.resetPage().addTagRefinement(tag),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Removes an numeric filter to an attribute with the `operator` and `value` provided. If the\n * filter is not set, it doesn't change the filters.\n *\n * Some parameters are optional, triggering different behavior:\n * - if the value is not provided, then all the numeric value will be removed for the\n * specified attribute/operator couple.\n * - if the operator is not provided either, then all the numeric filter on this attribute\n * will be removed.\n *\n * This method resets the current page to 0.\n * @param {string} attribute the attribute on which the numeric filter applies\n * @param {string} [operator] the operator of the filter\n * @param {number} [value] the value of the filter\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.removeNumericRefinement = function(attribute, operator, value) {\n this._change({\n state: this.state.resetPage().removeNumericRefinement(attribute, operator, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Removes a disjunctive filter to a faceted attribute with the `value` provided. If the\n * filter is not set, it doesn't change the filters.\n *\n * If the value is omitted, then this method will remove all the filters for the\n * attribute.\n *\n * This method resets the current page to 0.\n * @param {string} facet the facet to refine\n * @param {string} [value] the associated value\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.removeDisjunctiveFacetRefinement = function(facet, value) {\n this._change({\n state: this.state.resetPage().removeDisjunctiveFacetRefinement(facet, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * @deprecated since version 2.4.0, see {@link AlgoliaSearchHelper#removeDisjunctiveFacetRefinement}\n */\nAlgoliaSearchHelper.prototype.removeDisjunctiveRefine = function() {\n return this.removeDisjunctiveFacetRefinement.apply(this, arguments);\n};\n\n/**\n * Removes the refinement set on a hierarchical facet.\n * @param {string} facet the facet name\n * @return {AlgoliaSearchHelper}\n * @throws Error if the facet is not defined or if the facet is not refined\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.removeHierarchicalFacetRefinement = function(facet) {\n this._change({\n state: this.state.resetPage().removeHierarchicalFacetRefinement(facet),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Removes a filter to a faceted attribute with the `value` provided. If the\n * filter is not set, it doesn't change the filters.\n *\n * If the value is omitted, then this method will remove all the filters for the\n * attribute.\n *\n * This method resets the current page to 0.\n * @param {string} facet the facet to refine\n * @param {string} [value] the associated value\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.removeFacetRefinement = function(facet, value) {\n this._change({\n state: this.state.resetPage().removeFacetRefinement(facet, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * @deprecated since version 2.4.0, see {@link AlgoliaSearchHelper#removeFacetRefinement}\n */\nAlgoliaSearchHelper.prototype.removeRefine = function() {\n return this.removeFacetRefinement.apply(this, arguments);\n};\n\n/**\n * Removes an exclusion filter to a faceted attribute with the `value` provided. If the\n * filter is not set, it doesn't change the filters.\n *\n * If the value is omitted, then this method will remove all the filters for the\n * attribute.\n *\n * This method resets the current page to 0.\n * @param {string} facet the facet to refine\n * @param {string} [value] the associated value\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.removeFacetExclusion = function(facet, value) {\n this._change({\n state: this.state.resetPage().removeExcludeRefinement(facet, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * @deprecated since version 2.4.0, see {@link AlgoliaSearchHelper#removeFacetExclusion}\n */\nAlgoliaSearchHelper.prototype.removeExclude = function() {\n return this.removeFacetExclusion.apply(this, arguments);\n};\n\n/**\n * Removes a tag filter with the `tag` provided. If the\n * filter is not set, it doesn't change the filters.\n *\n * This method resets the current page to 0.\n * @param {string} tag tag to remove from the filter\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.removeTag = function(tag) {\n this._change({\n state: this.state.resetPage().removeTagRefinement(tag),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Adds or removes an exclusion filter to a faceted attribute with the `value` provided. If\n * the value is set then it removes it, otherwise it adds the filter.\n *\n * This method resets the current page to 0.\n * @param {string} facet the facet to refine\n * @param {string} value the associated value\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.toggleFacetExclusion = function(facet, value) {\n this._change({\n state: this.state.resetPage().toggleExcludeFacetRefinement(facet, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * @deprecated since version 2.4.0, see {@link AlgoliaSearchHelper#toggleFacetExclusion}\n */\nAlgoliaSearchHelper.prototype.toggleExclude = function() {\n return this.toggleFacetExclusion.apply(this, arguments);\n};\n\n/**\n * Adds or removes a filter to a faceted attribute with the `value` provided. If\n * the value is set then it removes it, otherwise it adds the filter.\n *\n * This method can be used for conjunctive, disjunctive and hierarchical filters.\n *\n * This method resets the current page to 0.\n * @param {string} facet the facet to refine\n * @param {string} value the associated value\n * @return {AlgoliaSearchHelper}\n * @throws Error will throw an error if the facet is not declared in the settings of the helper\n * @fires change\n * @chainable\n * @deprecated since version 2.19.0, see {@link AlgoliaSearchHelper#toggleFacetRefinement}\n */\nAlgoliaSearchHelper.prototype.toggleRefinement = function(facet, value) {\n return this.toggleFacetRefinement(facet, value);\n};\n\n/**\n * Adds or removes a filter to a faceted attribute with the `value` provided. If\n * the value is set then it removes it, otherwise it adds the filter.\n *\n * This method can be used for conjunctive, disjunctive and hierarchical filters.\n *\n * This method resets the current page to 0.\n * @param {string} facet the facet to refine\n * @param {string} value the associated value\n * @return {AlgoliaSearchHelper}\n * @throws Error will throw an error if the facet is not declared in the settings of the helper\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.toggleFacetRefinement = function(facet, value) {\n this._change({\n state: this.state.resetPage().toggleFacetRefinement(facet, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * @deprecated since version 2.4.0, see {@link AlgoliaSearchHelper#toggleFacetRefinement}\n */\nAlgoliaSearchHelper.prototype.toggleRefine = function() {\n return this.toggleFacetRefinement.apply(this, arguments);\n};\n\n/**\n * Adds or removes a tag filter with the `value` provided. If\n * the value is set then it removes it, otherwise it adds the filter.\n *\n * This method resets the current page to 0.\n * @param {string} tag tag to remove or add\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.toggleTag = function(tag) {\n this._change({\n state: this.state.resetPage().toggleTagRefinement(tag),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Increments the page number by one.\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n * @example\n * helper.setPage(0).nextPage().getPage();\n * // returns 1\n */\nAlgoliaSearchHelper.prototype.nextPage = function() {\n var page = this.state.page || 0;\n return this.setPage(page + 1);\n};\n\n/**\n * Decrements the page number by one.\n * @fires change\n * @return {AlgoliaSearchHelper}\n * @chainable\n * @example\n * helper.setPage(1).previousPage().getPage();\n * // returns 0\n */\nAlgoliaSearchHelper.prototype.previousPage = function() {\n var page = this.state.page || 0;\n return this.setPage(page - 1);\n};\n\n/**\n * @private\n */\nfunction setCurrentPage(page) {\n if (page < 0) throw new Error('Page requested below 0.');\n\n this._change({\n state: this.state.setPage(page),\n isPageReset: false\n });\n\n return this;\n}\n\n/**\n * Change the current page\n * @deprecated\n * @param {number} page The page number\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.setCurrentPage = setCurrentPage;\n\n/**\n * Updates the current page.\n * @function\n * @param {number} page The page number\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.setPage = setCurrentPage;\n\n/**\n * Updates the name of the index that will be targeted by the query.\n *\n * This method resets the current page to 0.\n * @param {string} name the index name\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.setIndex = function(name) {\n this._change({\n state: this.state.resetPage().setIndex(name),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Update a parameter of the search. This method reset the page\n *\n * The complete list of parameters is available on the\n * [Algolia website](https://www.algolia.com/doc/rest#query-an-index).\n * The most commonly used parameters have their own [shortcuts](#query-parameters-shortcuts)\n * or benefit from higher-level APIs (all the kind of filters and facets have their own API)\n *\n * This method resets the current page to 0.\n * @param {string} parameter name of the parameter to update\n * @param {any} value new value of the parameter\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n * @example\n * helper.setQueryParameter('hitsPerPage', 20).search();\n */\nAlgoliaSearchHelper.prototype.setQueryParameter = function(parameter, value) {\n this._change({\n state: this.state.resetPage().setQueryParameter(parameter, value),\n isPageReset: true\n });\n\n return this;\n};\n\n/**\n * Set the whole state (warning: will erase previous state)\n * @param {SearchParameters} newState the whole new state\n * @return {AlgoliaSearchHelper}\n * @fires change\n * @chainable\n */\nAlgoliaSearchHelper.prototype.setState = function(newState) {\n this._change({\n state: SearchParameters.make(newState),\n isPageReset: false\n });\n\n return this;\n};\n\n/**\n * Override the current state without triggering a change event.\n * Do not use this method unless you know what you are doing. (see the example\n * for a legit use case)\n * @param {SearchParameters} newState the whole new state\n * @return {AlgoliaSearchHelper}\n * @example\n * helper.on('change', function(state){\n * // In this function you might want to find a way to store the state in the url/history\n * updateYourURL(state)\n * })\n * window.onpopstate = function(event){\n * // This is naive though as you should check if the state is really defined etc.\n * helper.overrideStateWithoutTriggeringChangeEvent(event.state).search()\n * }\n * @chainable\n */\nAlgoliaSearchHelper.prototype.overrideStateWithoutTriggeringChangeEvent = function(newState) {\n this.state = new SearchParameters(newState);\n return this;\n};\n\n/**\n * Check if an attribute has any numeric, conjunctive, disjunctive or hierarchical filters.\n * @param {string} attribute the name of the attribute\n * @return {boolean} true if the attribute is filtered by at least one value\n * @example\n * // hasRefinements works with numeric, conjunctive, disjunctive and hierarchical filters\n * helper.hasRefinements('price'); // false\n * helper.addNumericRefinement('price', '>', 100);\n * helper.hasRefinements('price'); // true\n *\n * helper.hasRefinements('color'); // false\n * helper.addFacetRefinement('color', 'blue');\n * helper.hasRefinements('color'); // true\n *\n * helper.hasRefinements('material'); // false\n * helper.addDisjunctiveFacetRefinement('material', 'plastic');\n * helper.hasRefinements('material'); // true\n *\n * helper.hasRefinements('categories'); // false\n * helper.toggleFacetRefinement('categories', 'kitchen > knife');\n * helper.hasRefinements('categories'); // true\n *\n */\nAlgoliaSearchHelper.prototype.hasRefinements = function(attribute) {\n if (objectHasKeys(this.state.getNumericRefinements(attribute))) {\n return true;\n } else if (this.state.isConjunctiveFacet(attribute)) {\n return this.state.isFacetRefined(attribute);\n } else if (this.state.isDisjunctiveFacet(attribute)) {\n return this.state.isDisjunctiveFacetRefined(attribute);\n } else if (this.state.isHierarchicalFacet(attribute)) {\n return this.state.isHierarchicalFacetRefined(attribute);\n }\n\n // there's currently no way to know that the user did call `addNumericRefinement` at some point\n // thus we cannot distinguish if there once was a numeric refinement that was cleared\n // so we will return false in every other situations to be consistent\n // while what we should do here is throw because we did not find the attribute in any type\n // of refinement\n return false;\n};\n\n/**\n * Check if a value is excluded for a specific faceted attribute. If the value\n * is omitted then the function checks if there is any excluding refinements.\n *\n * @param {string} facet name of the attribute for used for faceting\n * @param {string} [value] optional value. If passed will test that this value\n * is filtering the given facet.\n * @return {boolean} true if refined\n * @example\n * helper.isExcludeRefined('color'); // false\n * helper.isExcludeRefined('color', 'blue') // false\n * helper.isExcludeRefined('color', 'red') // false\n *\n * helper.addFacetExclusion('color', 'red');\n *\n * helper.isExcludeRefined('color'); // true\n * helper.isExcludeRefined('color', 'blue') // false\n * helper.isExcludeRefined('color', 'red') // true\n */\nAlgoliaSearchHelper.prototype.isExcluded = function(facet, value) {\n return this.state.isExcludeRefined(facet, value);\n};\n\n/**\n * @deprecated since 2.4.0, see {@link AlgoliaSearchHelper#hasRefinements}\n */\nAlgoliaSearchHelper.prototype.isDisjunctiveRefined = function(facet, value) {\n return this.state.isDisjunctiveFacetRefined(facet, value);\n};\n\n/**\n * Check if the string is a currently filtering tag.\n * @param {string} tag tag to check\n * @return {boolean}\n */\nAlgoliaSearchHelper.prototype.hasTag = function(tag) {\n return this.state.isTagRefined(tag);\n};\n\n/**\n * @deprecated since 2.4.0, see {@link AlgoliaSearchHelper#hasTag}\n */\nAlgoliaSearchHelper.prototype.isTagRefined = function() {\n return this.hasTagRefinements.apply(this, arguments);\n};\n\n\n/**\n * Get the name of the currently used index.\n * @return {string}\n * @example\n * helper.setIndex('highestPrice_products').getIndex();\n * // returns 'highestPrice_products'\n */\nAlgoliaSearchHelper.prototype.getIndex = function() {\n return this.state.index;\n};\n\nfunction getCurrentPage() {\n return this.state.page;\n}\n\n/**\n * Get the currently selected page\n * @deprecated\n * @return {number} the current page\n */\nAlgoliaSearchHelper.prototype.getCurrentPage = getCurrentPage;\n/**\n * Get the currently selected page\n * @function\n * @return {number} the current page\n */\nAlgoliaSearchHelper.prototype.getPage = getCurrentPage;\n\n/**\n * Get all the tags currently set to filters the results.\n *\n * @return {string[]} The list of tags currently set.\n */\nAlgoliaSearchHelper.prototype.getTags = function() {\n return this.state.tagRefinements;\n};\n\n/**\n * Get the list of refinements for a given attribute. This method works with\n * conjunctive, disjunctive, excluding and numerical filters.\n *\n * See also SearchResults#getRefinements\n *\n * @param {string} facetName attribute name used for faceting\n * @return {Array.} All Refinement are objects that contain a value, and\n * a type. Numeric also contains an operator.\n * @example\n * helper.addNumericRefinement('price', '>', 100);\n * helper.getRefinements('price');\n * // [\n * // {\n * // \"value\": [\n * // 100\n * // ],\n * // \"operator\": \">\",\n * // \"type\": \"numeric\"\n * // }\n * // ]\n * @example\n * helper.addFacetRefinement('color', 'blue');\n * helper.addFacetExclusion('color', 'red');\n * helper.getRefinements('color');\n * // [\n * // {\n * // \"value\": \"blue\",\n * // \"type\": \"conjunctive\"\n * // },\n * // {\n * // \"value\": \"red\",\n * // \"type\": \"exclude\"\n * // }\n * // ]\n * @example\n * helper.addDisjunctiveFacetRefinement('material', 'plastic');\n * // [\n * // {\n * // \"value\": \"plastic\",\n * // \"type\": \"disjunctive\"\n * // }\n * // ]\n */\nAlgoliaSearchHelper.prototype.getRefinements = function(facetName) {\n var refinements = [];\n\n if (this.state.isConjunctiveFacet(facetName)) {\n var conjRefinements = this.state.getConjunctiveRefinements(facetName);\n\n conjRefinements.forEach(function(r) {\n refinements.push({\n value: r,\n type: 'conjunctive'\n });\n });\n\n var excludeRefinements = this.state.getExcludeRefinements(facetName);\n\n excludeRefinements.forEach(function(r) {\n refinements.push({\n value: r,\n type: 'exclude'\n });\n });\n } else if (this.state.isDisjunctiveFacet(facetName)) {\n var disjRefinements = this.state.getDisjunctiveRefinements(facetName);\n\n disjRefinements.forEach(function(r) {\n refinements.push({\n value: r,\n type: 'disjunctive'\n });\n });\n }\n\n var numericRefinements = this.state.getNumericRefinements(facetName);\n\n Object.keys(numericRefinements).forEach(function(operator) {\n var value = numericRefinements[operator];\n\n refinements.push({\n value: value,\n operator: operator,\n type: 'numeric'\n });\n });\n\n return refinements;\n};\n\n/**\n * Return the current refinement for the (attribute, operator)\n * @param {string} attribute attribute in the record\n * @param {string} operator operator applied on the refined values\n * @return {Array.} refined values\n */\nAlgoliaSearchHelper.prototype.getNumericRefinement = function(attribute, operator) {\n return this.state.getNumericRefinement(attribute, operator);\n};\n\n/**\n * Get the current breadcrumb for a hierarchical facet, as an array\n * @param {string} facetName Hierarchical facet name\n * @return {array.} the path as an array of string\n */\nAlgoliaSearchHelper.prototype.getHierarchicalFacetBreadcrumb = function(facetName) {\n return this.state.getHierarchicalFacetBreadcrumb(facetName);\n};\n\n// /////////// PRIVATE\n\n/**\n * Perform the underlying queries\n * @private\n * @return {undefined}\n * @fires search\n * @fires result\n * @fires error\n */\nAlgoliaSearchHelper.prototype._search = function(options) {\n var state = this.state;\n var states = [];\n var mainQueries = [];\n\n if (!options.onlyWithDerivedHelpers) {\n mainQueries = requestBuilder._getQueries(state.index, state);\n\n states.push({\n state: state,\n queriesCount: mainQueries.length,\n helper: this\n });\n\n this.emit('search', {\n state: state,\n results: this.lastResults\n });\n }\n\n var derivedQueries = this.derivedHelpers.map(function(derivedHelper) {\n var derivedState = derivedHelper.getModifiedState(state);\n var derivedStateQueries = requestBuilder._getQueries(derivedState.index, derivedState);\n\n states.push({\n state: derivedState,\n queriesCount: derivedStateQueries.length,\n helper: derivedHelper\n });\n\n derivedHelper.emit('search', {\n state: derivedState,\n results: derivedHelper.lastResults\n });\n\n return derivedStateQueries;\n });\n\n var queries = Array.prototype.concat.apply(mainQueries, derivedQueries);\n var queryId = this._queryId++;\n\n this._currentNbQueries++;\n\n try {\n this.client.search(queries)\n .then(this._dispatchAlgoliaResponse.bind(this, states, queryId))\n .catch(this._dispatchAlgoliaError.bind(this, queryId));\n } catch (error) {\n // If we reach this part, we're in an internal error state\n this.emit('error', {\n error: error\n });\n }\n};\n\n/**\n * Transform the responses as sent by the server and transform them into a user\n * usable object that merge the results of all the batch requests. It will dispatch\n * over the different helper + derived helpers (when there are some).\n * @private\n * @param {array.<{SearchParameters, AlgoliaQueries, AlgoliaSearchHelper}>}\n * state state used for to generate the request\n * @param {number} queryId id of the current request\n * @param {object} content content of the response\n * @return {undefined}\n */\nAlgoliaSearchHelper.prototype._dispatchAlgoliaResponse = function(states, queryId, content) {\n // FIXME remove the number of outdated queries discarded instead of just one\n\n if (queryId < this._lastQueryIdReceived) {\n // Outdated answer\n return;\n }\n\n this._currentNbQueries -= (queryId - this._lastQueryIdReceived);\n this._lastQueryIdReceived = queryId;\n\n if (this._currentNbQueries === 0) this.emit('searchQueueEmpty');\n\n var results = content.results.slice();\n\n states.forEach(function(s) {\n var state = s.state;\n var queriesCount = s.queriesCount;\n var helper = s.helper;\n var specificResults = results.splice(0, queriesCount);\n\n var formattedResponse = helper.lastResults = new SearchResults(state, specificResults);\n\n helper.emit('result', {\n results: formattedResponse,\n state: state\n });\n });\n};\n\nAlgoliaSearchHelper.prototype._dispatchAlgoliaError = function(queryId, error) {\n if (queryId < this._lastQueryIdReceived) {\n // Outdated answer\n return;\n }\n\n this._currentNbQueries -= queryId - this._lastQueryIdReceived;\n this._lastQueryIdReceived = queryId;\n\n this.emit('error', {\n error: error\n });\n\n if (this._currentNbQueries === 0) this.emit('searchQueueEmpty');\n};\n\nAlgoliaSearchHelper.prototype.containsRefinement = function(query, facetFilters, numericFilters, tagFilters) {\n return query ||\n facetFilters.length !== 0 ||\n numericFilters.length !== 0 ||\n tagFilters.length !== 0;\n};\n\n/**\n * Test if there are some disjunctive refinements on the facet\n * @private\n * @param {string} facet the attribute to test\n * @return {boolean}\n */\nAlgoliaSearchHelper.prototype._hasDisjunctiveRefinements = function(facet) {\n return this.state.disjunctiveRefinements[facet] &&\n this.state.disjunctiveRefinements[facet].length > 0;\n};\n\nAlgoliaSearchHelper.prototype._change = function(event) {\n var state = event.state;\n var isPageReset = event.isPageReset;\n\n if (state !== this.state) {\n this.state = state;\n\n this.emit('change', {\n state: this.state,\n results: this.lastResults,\n isPageReset: isPageReset\n });\n }\n};\n\n/**\n * Clears the cache of the underlying Algolia client.\n * @return {AlgoliaSearchHelper}\n */\nAlgoliaSearchHelper.prototype.clearCache = function() {\n this.client.clearCache && this.client.clearCache();\n return this;\n};\n\n/**\n * Updates the internal client instance. If the reference of the clients\n * are equal then no update is actually done.\n * @param {AlgoliaSearch} newClient an AlgoliaSearch client\n * @return {AlgoliaSearchHelper}\n */\nAlgoliaSearchHelper.prototype.setClient = function(newClient) {\n if (this.client === newClient) return this;\n\n if (typeof newClient.addAlgoliaAgent === 'function') {\n newClient.addAlgoliaAgent('JS Helper (' + version + ')');\n }\n this.client = newClient;\n\n return this;\n};\n\n/**\n * Gets the instance of the currently used client.\n * @return {AlgoliaSearch}\n */\nAlgoliaSearchHelper.prototype.getClient = function() {\n return this.client;\n};\n\n/**\n * Creates an derived instance of the Helper. A derived helper\n * is a way to request other indices synchronised with the lifecycle\n * of the main Helper. This mechanism uses the multiqueries feature\n * of Algolia to aggregate all the requests in a single network call.\n *\n * This method takes a function that is used to create a new SearchParameter\n * that will be used to create requests to Algolia. Those new requests\n * are created just before the `search` event. The signature of the function\n * is `SearchParameters -> SearchParameters`.\n *\n * This method returns a new DerivedHelper which is an EventEmitter\n * that fires the same `search`, `result` and `error` events. Those\n * events, however, will receive data specific to this DerivedHelper\n * and the SearchParameters that is returned by the call of the\n * parameter function.\n * @param {function} fn SearchParameters -> SearchParameters\n * @return {DerivedHelper}\n */\nAlgoliaSearchHelper.prototype.derive = function(fn) {\n var derivedHelper = new DerivedHelper(this, fn);\n this.derivedHelpers.push(derivedHelper);\n return derivedHelper;\n};\n\n/**\n * This method detaches a derived Helper from the main one. Prefer using the one from the\n * derived helper itself, to remove the event listeners too.\n * @private\n * @return {undefined}\n * @throws Error\n */\nAlgoliaSearchHelper.prototype.detachDerivedHelper = function(derivedHelper) {\n var pos = this.derivedHelpers.indexOf(derivedHelper);\n if (pos === -1) throw new Error('Derived helper already detached');\n this.derivedHelpers.splice(pos, 1);\n};\n\n/**\n * This method returns true if there is currently at least one on-going search.\n * @return {boolean} true if there is a search pending\n */\nAlgoliaSearchHelper.prototype.hasPendingRequests = function() {\n return this._currentNbQueries > 0;\n};\n\n/**\n * @typedef AlgoliaSearchHelper.NumericRefinement\n * @type {object}\n * @property {number[]} value the numbers that are used for filtering this attribute with\n * the operator specified.\n * @property {string} operator the faceting data: value, number of entries\n * @property {string} type will be 'numeric'\n */\n\n/**\n * @typedef AlgoliaSearchHelper.FacetRefinement\n * @type {object}\n * @property {string} value the string use to filter the attribute\n * @property {string} type the type of filter: 'conjunctive', 'disjunctive', 'exclude'\n */\n\nmodule.exports = AlgoliaSearchHelper;\n","'use strict';\n\nvar AlgoliaSearchHelper = require('./src/algoliasearch.helper');\n\nvar SearchParameters = require('./src/SearchParameters');\nvar SearchResults = require('./src/SearchResults');\n\n/**\n * The algoliasearchHelper module is the function that will let its\n * contains everything needed to use the Algoliasearch\n * Helper. It is a also a function that instanciate the helper.\n * To use the helper, you also need the Algolia JS client v3.\n * @example\n * //using the UMD build\n * var client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76');\n * var helper = algoliasearchHelper(client, 'bestbuy', {\n * facets: ['shipping'],\n * disjunctiveFacets: ['category']\n * });\n * helper.on('result', function(event) {\n * console.log(event.results);\n * });\n * helper\n * .toggleFacetRefinement('category', 'Movies & TV Shows')\n * .toggleFacetRefinement('shipping', 'Free shipping')\n * .search();\n * @example\n * // The helper is an event emitter using the node API\n * helper.on('result', updateTheResults);\n * helper.once('result', updateTheResults);\n * helper.removeListener('result', updateTheResults);\n * helper.removeAllListeners('result');\n * @module algoliasearchHelper\n * @param {AlgoliaSearch} client an AlgoliaSearch client\n * @param {string} index the name of the index to query\n * @param {SearchParameters|object} opts an object defining the initial config of the search. It doesn't have to be a {SearchParameters}, just an object containing the properties you need from it.\n * @return {AlgoliaSearchHelper}\n */\nfunction algoliasearchHelper(client, index, opts) {\n return new AlgoliaSearchHelper(client, index, opts);\n}\n\n/**\n * The version currently used\n * @member module:algoliasearchHelper.version\n * @type {number}\n */\nalgoliasearchHelper.version = require('./src/version.js');\n\n/**\n * Constructor for the Helper.\n * @member module:algoliasearchHelper.AlgoliaSearchHelper\n * @type {AlgoliaSearchHelper}\n */\nalgoliasearchHelper.AlgoliaSearchHelper = AlgoliaSearchHelper;\n\n/**\n * Constructor for the object containing all the parameters of the search.\n * @member module:algoliasearchHelper.SearchParameters\n * @type {SearchParameters}\n */\nalgoliasearchHelper.SearchParameters = SearchParameters;\n\n/**\n * Constructor for the object containing the results of the search.\n * @member module:algoliasearchHelper.SearchResults\n * @type {SearchResults}\n */\nalgoliasearchHelper.SearchResults = SearchResults;\n\nmodule.exports = algoliasearchHelper;\n","const nextMicroTask = Promise.resolve();\n\ntype Callback = (...args: any[]) => void;\ntype Defer = Callback & {\n wait(): Promise;\n cancel(): void;\n};\n\nconst defer = (callback: Callback): Defer => {\n let progress: Promise | null = null;\n let cancelled = false;\n\n const fn: Defer = (...args) => {\n if (progress !== null) {\n return;\n }\n\n progress = nextMicroTask.then(() => {\n progress = null;\n\n if (cancelled) {\n cancelled = false;\n return;\n }\n\n callback(...args);\n });\n };\n\n fn.wait = () => {\n if (progress === null) {\n throw new Error(\n 'The deferred function should be called before calling `wait()`'\n );\n }\n\n return progress;\n };\n\n fn.cancel = () => {\n if (progress === null) {\n return;\n }\n\n cancelled = true;\n };\n\n return fn;\n};\n\nexport default defer;\n","import isDomElement from './isDomElement';\n\n/**\n * Return the container. If it's a string, it is considered a\n * css selector and retrieves the first matching element. Otherwise\n * test if it validates that it's a correct DOMElement.\n *\n * @param {string|HTMLElement} selectorOrHTMLElement CSS Selector or container node.\n * @return {HTMLElement} Container node\n * @throws Error when the type is not correct\n */\nfunction getContainerNode(\n selectorOrHTMLElement: string | HTMLElement\n): HTMLElement {\n const isSelectorString = typeof selectorOrHTMLElement === 'string';\n const domElement = isSelectorString\n ? document.querySelector(selectorOrHTMLElement as string)\n : selectorOrHTMLElement;\n\n if (!isDomElement(domElement)) {\n let errorMessage = 'Container must be `string` or `HTMLElement`.';\n\n if (isSelectorString) {\n errorMessage += ` Unable to find ${selectorOrHTMLElement}`;\n }\n\n throw new Error(errorMessage);\n }\n\n return domElement;\n}\n\nexport default getContainerNode;\n","function isDomElement(object: any): object is HTMLElement {\n return (\n object instanceof HTMLElement || (Boolean(object) && object.nodeType > 0)\n );\n}\n\nexport default isDomElement;\n","function isSpecialClick(event: MouseEvent): boolean {\n const isMiddleClick = event.button === 1;\n\n return (\n isMiddleClick ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey\n );\n}\n\nexport default isSpecialClick;\n","function uniq(array: TItem[]): TItem[] {\n return array.filter((value, index, self) => self.indexOf(value) === index);\n}\n\nexport default uniq;\n","import uniq from './uniq';\nimport { Template } from '../../types';\n\ntype TemplatesConfig = object;\n\ntype Templates = {\n [key: string]: Template;\n};\n\ntype TemplateProps = {\n defaultTemplates: Templates;\n templates: Templates;\n templatesConfig: TemplatesConfig;\n};\n\ntype PreparedTemplateProps = {\n templatesConfig: TemplatesConfig;\n templates: Templates;\n useCustomCompileOptions: { [key: string]: boolean };\n};\n\nfunction prepareTemplates(\n defaultTemplates: Templates = {},\n templates: Templates = {}\n) {\n const allKeys = uniq([\n ...Object.keys(defaultTemplates),\n ...Object.keys(templates),\n ]);\n\n return allKeys.reduce(\n (config, key) => {\n const defaultTemplate = defaultTemplates[key];\n const customTemplate = templates[key];\n const isCustomTemplate =\n customTemplate !== undefined && customTemplate !== defaultTemplate;\n\n config.templates[key] = isCustomTemplate\n ? customTemplate\n : defaultTemplate;\n config.useCustomCompileOptions[key] = isCustomTemplate;\n\n return config;\n },\n {\n templates: {} as Templates,\n useCustomCompileOptions: {} as { [key: string]: boolean },\n }\n );\n}\n\n/**\n * Prepares an object to be passed to the Template widget\n */\nfunction prepareTemplateProps({\n defaultTemplates,\n templates,\n templatesConfig,\n}: TemplateProps): PreparedTemplateProps {\n const preparedTemplates = prepareTemplates(defaultTemplates, templates);\n\n return {\n templatesConfig,\n ...preparedTemplates,\n };\n}\n\nexport default prepareTemplateProps;\n","/*\n * Copyright 2011 Twitter, Inc.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n(function (Hogan) {\n // Setup regex assignments\n // remove whitespace according to Mustache spec\n var rIsWhitespace = /\\S/,\n rQuot = /\\\"/g,\n rNewline = /\\n/g,\n rCr = /\\r/g,\n rSlash = /\\\\/g,\n rLineSep = /\\u2028/,\n rParagraphSep = /\\u2029/;\n\n Hogan.tags = {\n '#': 1, '^': 2, '<': 3, '$': 4,\n '/': 5, '!': 6, '>': 7, '=': 8, '_v': 9,\n '{': 10, '&': 11, '_t': 12\n };\n\n Hogan.scan = function scan(text, delimiters) {\n var len = text.length,\n IN_TEXT = 0,\n IN_TAG_TYPE = 1,\n IN_TAG = 2,\n state = IN_TEXT,\n tagType = null,\n tag = null,\n buf = '',\n tokens = [],\n seenTag = false,\n i = 0,\n lineStart = 0,\n otag = '{{',\n ctag = '}}';\n\n function addBuf() {\n if (buf.length > 0) {\n tokens.push({tag: '_t', text: new String(buf)});\n buf = '';\n }\n }\n\n function lineIsWhitespace() {\n var isAllWhitespace = true;\n for (var j = lineStart; j < tokens.length; j++) {\n isAllWhitespace =\n (Hogan.tags[tokens[j].tag] < Hogan.tags['_v']) ||\n (tokens[j].tag == '_t' && tokens[j].text.match(rIsWhitespace) === null);\n if (!isAllWhitespace) {\n return false;\n }\n }\n\n return isAllWhitespace;\n }\n\n function filterLine(haveSeenTag, noNewLine) {\n addBuf();\n\n if (haveSeenTag && lineIsWhitespace()) {\n for (var j = lineStart, next; j < tokens.length; j++) {\n if (tokens[j].text) {\n if ((next = tokens[j+1]) && next.tag == '>') {\n // set indent to token value\n next.indent = tokens[j].text.toString()\n }\n tokens.splice(j, 1);\n }\n }\n } else if (!noNewLine) {\n tokens.push({tag:'\\n'});\n }\n\n seenTag = false;\n lineStart = tokens.length;\n }\n\n function changeDelimiters(text, index) {\n var close = '=' + ctag,\n closeIndex = text.indexOf(close, index),\n delimiters = trim(\n text.substring(text.indexOf('=', index) + 1, closeIndex)\n ).split(' ');\n\n otag = delimiters[0];\n ctag = delimiters[delimiters.length - 1];\n\n return closeIndex + close.length - 1;\n }\n\n if (delimiters) {\n delimiters = delimiters.split(' ');\n otag = delimiters[0];\n ctag = delimiters[1];\n }\n\n for (i = 0; i < len; i++) {\n if (state == IN_TEXT) {\n if (tagChange(otag, text, i)) {\n --i;\n addBuf();\n state = IN_TAG_TYPE;\n } else {\n if (text.charAt(i) == '\\n') {\n filterLine(seenTag);\n } else {\n buf += text.charAt(i);\n }\n }\n } else if (state == IN_TAG_TYPE) {\n i += otag.length - 1;\n tag = Hogan.tags[text.charAt(i + 1)];\n tagType = tag ? text.charAt(i + 1) : '_v';\n if (tagType == '=') {\n i = changeDelimiters(text, i);\n state = IN_TEXT;\n } else {\n if (tag) {\n i++;\n }\n state = IN_TAG;\n }\n seenTag = i;\n } else {\n if (tagChange(ctag, text, i)) {\n tokens.push({tag: tagType, n: trim(buf), otag: otag, ctag: ctag,\n i: (tagType == '/') ? seenTag - otag.length : i + ctag.length});\n buf = '';\n i += ctag.length - 1;\n state = IN_TEXT;\n if (tagType == '{') {\n if (ctag == '}}') {\n i++;\n } else {\n cleanTripleStache(tokens[tokens.length - 1]);\n }\n }\n } else {\n buf += text.charAt(i);\n }\n }\n }\n\n filterLine(seenTag, true);\n\n return tokens;\n }\n\n function cleanTripleStache(token) {\n if (token.n.substr(token.n.length - 1) === '}') {\n token.n = token.n.substring(0, token.n.length - 1);\n }\n }\n\n function trim(s) {\n if (s.trim) {\n return s.trim();\n }\n\n return s.replace(/^\\s*|\\s*$/g, '');\n }\n\n function tagChange(tag, text, index) {\n if (text.charAt(index) != tag.charAt(0)) {\n return false;\n }\n\n for (var i = 1, l = tag.length; i < l; i++) {\n if (text.charAt(index + i) != tag.charAt(i)) {\n return false;\n }\n }\n\n return true;\n }\n\n // the tags allowed inside super templates\n var allowedInSuper = {'_t': true, '\\n': true, '$': true, '/': true};\n\n function buildTree(tokens, kind, stack, customTags) {\n var instructions = [],\n opener = null,\n tail = null,\n token = null;\n\n tail = stack[stack.length - 1];\n\n while (tokens.length > 0) {\n token = tokens.shift();\n\n if (tail && tail.tag == '<' && !(token.tag in allowedInSuper)) {\n throw new Error('Illegal content in < super tag.');\n }\n\n if (Hogan.tags[token.tag] <= Hogan.tags['$'] || isOpener(token, customTags)) {\n stack.push(token);\n token.nodes = buildTree(tokens, token.tag, stack, customTags);\n } else if (token.tag == '/') {\n if (stack.length === 0) {\n throw new Error('Closing tag without opener: /' + token.n);\n }\n opener = stack.pop();\n if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) {\n throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n);\n }\n opener.end = token.i;\n return instructions;\n } else if (token.tag == '\\n') {\n token.last = (tokens.length == 0) || (tokens[0].tag == '\\n');\n }\n\n instructions.push(token);\n }\n\n if (stack.length > 0) {\n throw new Error('missing closing tag: ' + stack.pop().n);\n }\n\n return instructions;\n }\n\n function isOpener(token, tags) {\n for (var i = 0, l = tags.length; i < l; i++) {\n if (tags[i].o == token.n) {\n token.tag = '#';\n return true;\n }\n }\n }\n\n function isCloser(close, open, tags) {\n for (var i = 0, l = tags.length; i < l; i++) {\n if (tags[i].c == close && tags[i].o == open) {\n return true;\n }\n }\n }\n\n function stringifySubstitutions(obj) {\n var items = [];\n for (var key in obj) {\n items.push('\"' + esc(key) + '\": function(c,p,t,i) {' + obj[key] + '}');\n }\n return \"{ \" + items.join(\",\") + \" }\";\n }\n\n function stringifyPartials(codeObj) {\n var partials = [];\n for (var key in codeObj.partials) {\n partials.push('\"' + esc(key) + '\":{name:\"' + esc(codeObj.partials[key].name) + '\", ' + stringifyPartials(codeObj.partials[key]) + \"}\");\n }\n return \"partials: {\" + partials.join(\",\") + \"}, subs: \" + stringifySubstitutions(codeObj.subs);\n }\n\n Hogan.stringify = function(codeObj, text, options) {\n return \"{code: function (c,p,i) { \" + Hogan.wrapMain(codeObj.code) + \" },\" + stringifyPartials(codeObj) + \"}\";\n }\n\n var serialNo = 0;\n Hogan.generate = function(tree, text, options) {\n serialNo = 0;\n var context = { code: '', subs: {}, partials: {} };\n Hogan.walk(tree, context);\n\n if (options.asString) {\n return this.stringify(context, text, options);\n }\n\n return this.makeTemplate(context, text, options);\n }\n\n Hogan.wrapMain = function(code) {\n return 'var t=this;t.b(i=i||\"\");' + code + 'return t.fl();';\n }\n\n Hogan.template = Hogan.Template;\n\n Hogan.makeTemplate = function(codeObj, text, options) {\n var template = this.makePartials(codeObj);\n template.code = new Function('c', 'p', 'i', this.wrapMain(codeObj.code));\n return new this.template(template, text, this, options);\n }\n\n Hogan.makePartials = function(codeObj) {\n var key, template = {subs: {}, partials: codeObj.partials, name: codeObj.name};\n for (key in template.partials) {\n template.partials[key] = this.makePartials(template.partials[key]);\n }\n for (key in codeObj.subs) {\n template.subs[key] = new Function('c', 'p', 't', 'i', codeObj.subs[key]);\n }\n return template;\n }\n\n function esc(s) {\n return s.replace(rSlash, '\\\\\\\\')\n .replace(rQuot, '\\\\\\\"')\n .replace(rNewline, '\\\\n')\n .replace(rCr, '\\\\r')\n .replace(rLineSep, '\\\\u2028')\n .replace(rParagraphSep, '\\\\u2029');\n }\n\n function chooseMethod(s) {\n return (~s.indexOf('.')) ? 'd' : 'f';\n }\n\n function createPartial(node, context) {\n var prefix = \"<\" + (context.prefix || \"\");\n var sym = prefix + node.n + serialNo++;\n context.partials[sym] = {name: node.n, partials: {}};\n context.code += 't.b(t.rp(\"' + esc(sym) + '\",c,p,\"' + (node.indent || '') + '\"));';\n return sym;\n }\n\n Hogan.codegen = {\n '#': function(node, context) {\n context.code += 'if(t.s(t.' + chooseMethod(node.n) + '(\"' + esc(node.n) + '\",c,p,1),' +\n 'c,p,0,' + node.i + ',' + node.end + ',\"' + node.otag + \" \" + node.ctag + '\")){' +\n 't.rs(c,p,' + 'function(c,p,t){';\n Hogan.walk(node.nodes, context);\n context.code += '});c.pop();}';\n },\n\n '^': function(node, context) {\n context.code += 'if(!t.s(t.' + chooseMethod(node.n) + '(\"' + esc(node.n) + '\",c,p,1),c,p,1,0,0,\"\")){';\n Hogan.walk(node.nodes, context);\n context.code += '};';\n },\n\n '>': createPartial,\n '<': function(node, context) {\n var ctx = {partials: {}, code: '', subs: {}, inPartial: true};\n Hogan.walk(node.nodes, ctx);\n var template = context.partials[createPartial(node, context)];\n template.subs = ctx.subs;\n template.partials = ctx.partials;\n },\n\n '$': function(node, context) {\n var ctx = {subs: {}, code: '', partials: context.partials, prefix: node.n};\n Hogan.walk(node.nodes, ctx);\n context.subs[node.n] = ctx.code;\n if (!context.inPartial) {\n context.code += 't.sub(\"' + esc(node.n) + '\",c,p,i);';\n }\n },\n\n '\\n': function(node, context) {\n context.code += write('\"\\\\n\"' + (node.last ? '' : ' + i'));\n },\n\n '_v': function(node, context) {\n context.code += 't.b(t.v(t.' + chooseMethod(node.n) + '(\"' + esc(node.n) + '\",c,p,0)));';\n },\n\n '_t': function(node, context) {\n context.code += write('\"' + esc(node.text) + '\"');\n },\n\n '{': tripleStache,\n\n '&': tripleStache\n }\n\n function tripleStache(node, context) {\n context.code += 't.b(t.t(t.' + chooseMethod(node.n) + '(\"' + esc(node.n) + '\",c,p,0)));';\n }\n\n function write(s) {\n return 't.b(' + s + ');';\n }\n\n Hogan.walk = function(nodelist, context) {\n var func;\n for (var i = 0, l = nodelist.length; i < l; i++) {\n func = Hogan.codegen[nodelist[i].tag];\n func && func(nodelist[i], context);\n }\n return context;\n }\n\n Hogan.parse = function(tokens, text, options) {\n options = options || {};\n return buildTree(tokens, '', [], options.sectionTags || []);\n }\n\n Hogan.cache = {};\n\n Hogan.cacheKey = function(text, options) {\n return [text, !!options.asString, !!options.disableLambda, options.delimiters, !!options.modelGet].join('||');\n }\n\n Hogan.compile = function(text, options) {\n options = options || {};\n var key = Hogan.cacheKey(text, options);\n var template = this.cache[key];\n\n if (template) {\n var partials = template.partials;\n for (var name in partials) {\n delete partials[name].instance;\n }\n return template;\n }\n\n template = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options);\n return this.cache[key] = template;\n }\n})(typeof exports !== 'undefined' ? exports : Hogan);\n","/*\n * Copyright 2011 Twitter, Inc.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nvar Hogan = {};\n\n(function (Hogan) {\n Hogan.Template = function (codeObj, text, compiler, options) {\n codeObj = codeObj || {};\n this.r = codeObj.code || this.r;\n this.c = compiler;\n this.options = options || {};\n this.text = text || '';\n this.partials = codeObj.partials || {};\n this.subs = codeObj.subs || {};\n this.buf = '';\n }\n\n Hogan.Template.prototype = {\n // render: replaced by generated code.\n r: function (context, partials, indent) { return ''; },\n\n // variable escaping\n v: hoganEscape,\n\n // triple stache\n t: coerceToString,\n\n render: function render(context, partials, indent) {\n return this.ri([context], partials || {}, indent);\n },\n\n // render internal -- a hook for overrides that catches partials too\n ri: function (context, partials, indent) {\n return this.r(context, partials, indent);\n },\n\n // ensurePartial\n ep: function(symbol, partials) {\n var partial = this.partials[symbol];\n\n // check to see that if we've instantiated this partial before\n var template = partials[partial.name];\n if (partial.instance && partial.base == template) {\n return partial.instance;\n }\n\n if (typeof template == 'string') {\n if (!this.c) {\n throw new Error(\"No compiler available.\");\n }\n template = this.c.compile(template, this.options);\n }\n\n if (!template) {\n return null;\n }\n\n // We use this to check whether the partials dictionary has changed\n this.partials[symbol].base = template;\n\n if (partial.subs) {\n // Make sure we consider parent template now\n if (!partials.stackText) partials.stackText = {};\n for (key in partial.subs) {\n if (!partials.stackText[key]) {\n partials.stackText[key] = (this.activeSub !== undefined && partials.stackText[this.activeSub]) ? partials.stackText[this.activeSub] : this.text;\n }\n }\n template = createSpecializedPartial(template, partial.subs, partial.partials,\n this.stackSubs, this.stackPartials, partials.stackText);\n }\n this.partials[symbol].instance = template;\n\n return template;\n },\n\n // tries to find a partial in the current scope and render it\n rp: function(symbol, context, partials, indent) {\n var partial = this.ep(symbol, partials);\n if (!partial) {\n return '';\n }\n\n return partial.ri(context, partials, indent);\n },\n\n // render a section\n rs: function(context, partials, section) {\n var tail = context[context.length - 1];\n\n if (!isArray(tail)) {\n section(context, partials, this);\n return;\n }\n\n for (var i = 0; i < tail.length; i++) {\n context.push(tail[i]);\n section(context, partials, this);\n context.pop();\n }\n },\n\n // maybe start a section\n s: function(val, ctx, partials, inverted, start, end, tags) {\n var pass;\n\n if (isArray(val) && val.length === 0) {\n return false;\n }\n\n if (typeof val == 'function') {\n val = this.ms(val, ctx, partials, inverted, start, end, tags);\n }\n\n pass = !!val;\n\n if (!inverted && pass && ctx) {\n ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]);\n }\n\n return pass;\n },\n\n // find values with dotted names\n d: function(key, ctx, partials, returnFound) {\n var found,\n names = key.split('.'),\n val = this.f(names[0], ctx, partials, returnFound),\n doModelGet = this.options.modelGet,\n cx = null;\n\n if (key === '.' && isArray(ctx[ctx.length - 2])) {\n val = ctx[ctx.length - 1];\n } else {\n for (var i = 1; i < names.length; i++) {\n found = findInScope(names[i], val, doModelGet);\n if (found !== undefined) {\n cx = val;\n val = found;\n } else {\n val = '';\n }\n }\n }\n\n if (returnFound && !val) {\n return false;\n }\n\n if (!returnFound && typeof val == 'function') {\n ctx.push(cx);\n val = this.mv(val, ctx, partials);\n ctx.pop();\n }\n\n return val;\n },\n\n // find values with normal names\n f: function(key, ctx, partials, returnFound) {\n var val = false,\n v = null,\n found = false,\n doModelGet = this.options.modelGet;\n\n for (var i = ctx.length - 1; i >= 0; i--) {\n v = ctx[i];\n val = findInScope(key, v, doModelGet);\n if (val !== undefined) {\n found = true;\n break;\n }\n }\n\n if (!found) {\n return (returnFound) ? false : \"\";\n }\n\n if (!returnFound && typeof val == 'function') {\n val = this.mv(val, ctx, partials);\n }\n\n return val;\n },\n\n // higher order templates\n ls: function(func, cx, partials, text, tags) {\n var oldTags = this.options.delimiters;\n\n this.options.delimiters = tags;\n this.b(this.ct(coerceToString(func.call(cx, text)), cx, partials));\n this.options.delimiters = oldTags;\n\n return false;\n },\n\n // compile text\n ct: function(text, cx, partials) {\n if (this.options.disableLambda) {\n throw new Error('Lambda features disabled.');\n }\n return this.c.compile(text, this.options).render(cx, partials);\n },\n\n // template result buffering\n b: function(s) { this.buf += s; },\n\n fl: function() { var r = this.buf; this.buf = ''; return r; },\n\n // method replace section\n ms: function(func, ctx, partials, inverted, start, end, tags) {\n var textSource,\n cx = ctx[ctx.length - 1],\n result = func.call(cx);\n\n if (typeof result == 'function') {\n if (inverted) {\n return true;\n } else {\n textSource = (this.activeSub && this.subsText && this.subsText[this.activeSub]) ? this.subsText[this.activeSub] : this.text;\n return this.ls(result, cx, partials, textSource.substring(start, end), tags);\n }\n }\n\n return result;\n },\n\n // method replace variable\n mv: function(func, ctx, partials) {\n var cx = ctx[ctx.length - 1];\n var result = func.call(cx);\n\n if (typeof result == 'function') {\n return this.ct(coerceToString(result.call(cx)), cx, partials);\n }\n\n return result;\n },\n\n sub: function(name, context, partials, indent) {\n var f = this.subs[name];\n if (f) {\n this.activeSub = name;\n f(context, partials, this, indent);\n this.activeSub = false;\n }\n }\n\n };\n\n //Find a key in an object\n function findInScope(key, scope, doModelGet) {\n var val;\n\n if (scope && typeof scope == 'object') {\n\n if (scope[key] !== undefined) {\n val = scope[key];\n\n // try lookup with get for backbone or similar model data\n } else if (doModelGet && scope.get && typeof scope.get == 'function') {\n val = scope.get(key);\n }\n }\n\n return val;\n }\n\n function createSpecializedPartial(instance, subs, partials, stackSubs, stackPartials, stackText) {\n function PartialTemplate() {};\n PartialTemplate.prototype = instance;\n function Substitutions() {};\n Substitutions.prototype = instance.subs;\n var key;\n var partial = new PartialTemplate();\n partial.subs = new Substitutions();\n partial.subsText = {}; //hehe. substext.\n partial.buf = '';\n\n stackSubs = stackSubs || {};\n partial.stackSubs = stackSubs;\n partial.subsText = stackText;\n for (key in subs) {\n if (!stackSubs[key]) stackSubs[key] = subs[key];\n }\n for (key in stackSubs) {\n partial.subs[key] = stackSubs[key];\n }\n\n stackPartials = stackPartials || {};\n partial.stackPartials = stackPartials;\n for (key in partials) {\n if (!stackPartials[key]) stackPartials[key] = partials[key];\n }\n for (key in stackPartials) {\n partial.partials[key] = stackPartials[key];\n }\n\n return partial;\n }\n\n var rAmp = /&/g,\n rLt = //g,\n rApos = /\\'/g,\n rQuot = /\\\"/g,\n hChars = /[&<>\\\"\\']/;\n\n function coerceToString(val) {\n return String((val === null || val === undefined) ? '' : val);\n }\n\n function hoganEscape(str) {\n str = coerceToString(str);\n return hChars.test(str) ?\n str\n .replace(rAmp, '&')\n .replace(rLt, '<')\n .replace(rGt, '>')\n .replace(rApos, ''')\n .replace(rQuot, '"') :\n str;\n }\n\n var isArray = Array.isArray || function(a) {\n return Object.prototype.toString.call(a) === '[object Array]';\n };\n\n})(typeof exports !== 'undefined' ? exports : Hogan);\n","/*\n * Copyright 2011 Twitter, Inc.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// This file is for use with Node.js. See dist/ for browser files.\n\nvar Hogan = require('./compiler');\nHogan.Template = require('./template').Template;\nHogan.template = Hogan.Template;\nmodule.exports = Hogan;\n","import hogan from 'hogan.js';\n\n// We add all our template helper methods to the template as lambdas. Note\n// that lambdas in Mustache are supposed to accept a second argument of\n// `render` to get the rendered value, not the literal `{{value}}`. But\n// this is currently broken (see https://github.com/twitter/hogan.js/issues/222).\nfunction transformHelpersToHogan(helpers = {}, compileOptions, data) {\n return Object.keys(helpers).reduce(\n (acc, helperKey) => ({\n ...acc,\n [helperKey]() {\n return text => {\n const render = value =>\n hogan.compile(value, compileOptions).render(this);\n\n return helpers[helperKey].call(data, text, render);\n };\n },\n }),\n {}\n );\n}\n\nfunction renderTemplate({\n templates,\n templateKey,\n compileOptions,\n helpers,\n data,\n bindEvent,\n}) {\n const template = templates[templateKey];\n const templateType = typeof template;\n const isTemplateString = templateType === 'string';\n const isTemplateFunction = templateType === 'function';\n\n if (!isTemplateString && !isTemplateFunction) {\n throw new Error(\n `Template must be 'string' or 'function', was '${templateType}' (key: ${templateKey})`\n );\n }\n\n if (isTemplateFunction) {\n return template(data, bindEvent);\n }\n\n const transformedHelpers = transformHelpersToHogan(\n helpers,\n compileOptions,\n data\n );\n\n return hogan\n .compile(template, compileOptions)\n .render({\n ...data,\n helpers: transformedHelpers,\n })\n .replace(/[ \\n\\r\\t\\f\\xA0]+/g, spaces =>\n spaces.replace(/(^|\\xA0+)[^\\xA0]+/g, '$1 ')\n )\n .trim();\n}\n\nexport default renderTemplate;\n","// We aren't using the native `Array.prototype.find` because the refactor away from Lodash is not\n// published as a major version.\n// Relying on the `find` polyfill on user-land, which before was only required for niche use-cases,\n// was decided as too risky.\n// @MAJOR Replace with the native `Array.prototype.find` method\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find\nfunction find(\n items: TItem[],\n predicate: (value: TItem, index: number, obj: TItem[]) => boolean\n): TItem | undefined {\n let value: TItem;\n for (let i = 0; i < items.length; i++) {\n value = items[i];\n // inlined for performance: if (Call(predicate, thisArg, [value, i, list])) {\n if (predicate(value, i, items)) {\n return value;\n }\n }\n\n return undefined;\n}\n\nexport default find;\n","function unescapeRefinement(value: string | number): string {\n return String(value).replace(/^\\\\-/, '-');\n}\n\nexport default unescapeRefinement;\n","import { SearchParameters, SearchResults } from 'algoliasearch-helper';\nimport find from './find';\nimport unescapeRefinement from './unescapeRefinement';\n\nexport type FacetRefinement = {\n type:\n | 'facet'\n | 'exclude'\n | 'disjunctive'\n | 'hierarchical'\n | 'numeric'\n | 'tag'\n | 'query';\n attribute: string;\n name: string;\n count?: number;\n exhaustive?: boolean;\n};\n\nexport type QueryRefinement = {\n type: 'query';\n query: string;\n} & Pick;\n\nexport type NumericRefinement = {\n type: 'numeric';\n numericValue: number;\n operator: '<' | '<=' | '=' | '!=' | '>=' | '>';\n} & FacetRefinement;\n\nexport type FacetExcludeRefinement = {\n type: 'exclude';\n exclude: boolean;\n} & FacetRefinement;\n\nexport type Refinement =\n | FacetRefinement\n | QueryRefinement\n | NumericRefinement\n | FacetExcludeRefinement;\n\nfunction getRefinement(\n state: SearchParameters,\n type: Refinement['type'],\n attribute: Refinement['attribute'],\n name: Refinement['name'],\n resultsFacets: SearchResults['facets' | 'hierarchicalFacets'] = []\n): Refinement {\n const res: Refinement = { type, attribute, name };\n let facet: any = find(\n resultsFacets as Array<{ name: string }>,\n resultsFacet => resultsFacet.name === attribute\n );\n let count: number;\n\n if (type === 'hierarchical') {\n const facetDeclaration = state.getHierarchicalFacetByName(attribute);\n const nameParts = name.split(facetDeclaration.separator);\n\n const getFacetRefinement = (\n facetData: any\n ): ((refinementKey: string) => any) => (refinementKey: string): any =>\n facetData[refinementKey];\n\n for (let i = 0; facet !== undefined && i < nameParts.length; ++i) {\n facet =\n facet &&\n facet.data &&\n find(\n Object.keys(facet.data).map(getFacetRefinement(facet.data)),\n refinement => refinement.name === nameParts[i]\n );\n }\n\n count = facet && facet.count;\n } else {\n count = facet && facet.data && facet.data[res.name];\n }\n\n const exhaustive = facet && facet.exhaustive;\n\n if (count !== undefined) {\n res.count = count;\n }\n\n if (exhaustive !== undefined) {\n res.exhaustive = exhaustive;\n }\n\n return res;\n}\n\nfunction getRefinements(\n results: SearchResults,\n state: SearchParameters,\n clearsQuery: boolean = false\n): Refinement[] {\n const refinements: Refinement[] = [];\n const {\n facetsRefinements = {},\n facetsExcludes = {},\n disjunctiveFacetsRefinements = {},\n hierarchicalFacetsRefinements = {},\n numericRefinements = {},\n tagRefinements = [],\n } = state;\n\n Object.keys(facetsRefinements).forEach(attribute => {\n const refinementNames = facetsRefinements[attribute];\n\n refinementNames.forEach(refinementName => {\n refinements.push(\n getRefinement(state, 'facet', attribute, refinementName, results.facets)\n );\n });\n });\n\n Object.keys(facetsExcludes).forEach(attribute => {\n const refinementNames = facetsExcludes[attribute];\n\n refinementNames.forEach(refinementName => {\n refinements.push({\n type: 'exclude',\n attribute,\n name: refinementName,\n exclude: true,\n });\n });\n });\n\n Object.keys(disjunctiveFacetsRefinements).forEach(attribute => {\n const refinementNames = disjunctiveFacetsRefinements[attribute];\n\n refinementNames.forEach(refinementName => {\n refinements.push(\n getRefinement(\n state,\n 'disjunctive',\n attribute,\n // We unescape any disjunctive refined values with `unescapeRefinement` because\n // they can be escaped on negative numeric values with `escapeRefinement`.\n unescapeRefinement(refinementName),\n results.disjunctiveFacets\n )\n );\n });\n });\n\n Object.keys(hierarchicalFacetsRefinements).forEach(attribute => {\n const refinementNames = hierarchicalFacetsRefinements[attribute];\n\n refinementNames.forEach(refinement => {\n refinements.push(\n getRefinement(\n state,\n 'hierarchical',\n attribute,\n refinement,\n results.hierarchicalFacets\n )\n );\n });\n });\n\n Object.keys(numericRefinements).forEach(attribute => {\n const operators = numericRefinements[attribute];\n\n Object.keys(operators).forEach(operatorOriginal => {\n const operator = operatorOriginal as SearchParameters.Operator;\n const valueOrValues = operators[operator];\n const refinementNames = Array.isArray(valueOrValues)\n ? valueOrValues\n : [valueOrValues];\n\n refinementNames.forEach(refinementName => {\n refinements.push({\n type: 'numeric',\n attribute,\n name: `${refinementName}`,\n numericValue: refinementName,\n operator: operator as NumericRefinement['operator'],\n });\n });\n });\n });\n\n tagRefinements.forEach(refinementName => {\n refinements.push({ type: 'tag', attribute: '_tags', name: refinementName });\n });\n\n if (clearsQuery && state.query && state.query.trim()) {\n refinements.push({\n attribute: 'query',\n type: 'query',\n name: state.query,\n query: state.query,\n });\n }\n\n return refinements;\n}\n\nexport default getRefinements;\n","import { AlgoliaSearchHelper } from 'algoliasearch-helper';\n\n/**\n * Clears the refinements of a SearchParameters object based on rules provided.\n * The included attributes list is applied before the excluded attributes list. If the list\n * is not provided, this list of all the currently refined attributes is used as included attributes.\n * @param {object} $0 parameters\n * @param {Helper} $0.helper instance of the Helper\n * @param {string[]} [$0.attributesToClear = []] list of parameters to clear\n * @returns {SearchParameters} search parameters with refinements cleared\n */\nfunction clearRefinements({\n helper,\n attributesToClear = [],\n}: {\n helper: AlgoliaSearchHelper;\n attributesToClear?: string[];\n}) {\n let finalState = helper.state.setPage(0);\n\n finalState = attributesToClear.reduce((state, attribute) => {\n if (finalState.isNumericRefined(attribute)) {\n return state.removeNumericRefinement(attribute);\n }\n if (finalState.isHierarchicalFacet(attribute)) {\n return state.removeHierarchicalFacetRefinement(attribute);\n }\n if (finalState.isDisjunctiveFacet(attribute)) {\n return state.removeDisjunctiveFacetRefinement(attribute);\n }\n if (finalState.isConjunctiveFacet(attribute)) {\n return state.removeFacetRefinement(attribute);\n }\n\n return state;\n }, finalState);\n\n if (attributesToClear.indexOf('query') !== -1) {\n finalState = finalState.setQuery('');\n }\n\n return finalState;\n}\n\nexport default clearRefinements;\n","function escapeRefinement(value: string | number): string | number {\n if (typeof value === 'number' && value < 0) {\n value = String(value).replace(/^-/, '\\\\-');\n }\n\n return value;\n}\n\nexport default escapeRefinement;\n","import { Renderer } from '../../types/connector';\nimport getObjectType from './getObjectType';\n\nfunction checkRendering(\n rendering: Renderer,\n usage: string\n): void {\n if (rendering === undefined || typeof rendering !== 'function') {\n throw new Error(`The render function is not valid (received type ${getObjectType(\n rendering\n )}).\n\n${usage}`);\n }\n}\n\nexport default checkRendering;\n","function getObjectType(object: unknown): string {\n return Object.prototype.toString.call(object).slice(8, -1);\n}\n\nexport default getObjectType;\n","function noop(..._args: any[]): void {}\n\nexport default noop;\n","function getPropertyByPath(\n object: object | undefined,\n path: string | string[]\n): any {\n const parts = Array.isArray(path) ? path : path.split('.');\n\n return parts.reduce((current, key) => current && current[key], object);\n}\n\nexport default getPropertyByPath;\n","// This is the `Number.isFinite()` polyfill recommended by MDN.\n// We do not provide any tests for this function.\n// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite#Polyfill\nfunction isFiniteNumber(value: any): value is number {\n return typeof value === 'number' && isFinite(value);\n}\n\nexport default isFiniteNumber;\n","/**\n * This implementation is taken from Lodash implementation.\n * See: https://github.com/lodash/lodash/blob/master/isPlainObject.js\n */\n\nfunction getTag(value: any): string {\n if (value === null) {\n return value === undefined ? '[object Undefined]' : '[object Null]';\n }\n\n return Object.prototype.toString.call(value);\n}\n\nfunction isObjectLike(value: any): boolean {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Checks if `value` is a plain object.\n *\n * A plain object is an object created by the `Object`\n * constructor or with a `[[Prototype]]` of `null`.\n */\nfunction isPlainObject(value: any): boolean {\n if (!isObjectLike(value) || getTag(value) !== '[object Object]') {\n return false;\n }\n\n if (Object.getPrototypeOf(value) === null) {\n return true;\n }\n\n let proto = value;\n\n while (Object.getPrototypeOf(proto) !== null) {\n proto = Object.getPrototypeOf(proto);\n }\n\n return Object.getPrototypeOf(value) === proto;\n}\n\nexport default isPlainObject;\n","type RangeOptions = {\n start?: number;\n end: number;\n step?: number;\n};\n\nfunction range({ start = 0, end, step = 1 }: RangeOptions): number[] {\n // We can't divide by 0 so we re-assign the step to 1 if it happens.\n const limitStep = step === 0 ? 1 : step;\n\n // In some cases the array to create has a decimal length.\n // We therefore need to round the value.\n // Example:\n // { start: 1, end: 5000, step: 500 }\n // => Array length = (5000 - 1) / 500 = 9.998\n const arrayLength = Math.round((end - start) / limitStep);\n\n return [...Array(arrayLength)].map(\n (_, current) => start + current * limitStep\n );\n}\n\nexport default range;\n","function isPrimitive(obj: any): boolean {\n return obj !== Object(obj);\n}\n\nfunction isEqual(first: any, second: any): boolean {\n if (first === second) {\n return true;\n }\n\n if (\n isPrimitive(first) ||\n isPrimitive(second) ||\n typeof first === 'function' ||\n typeof second === 'function'\n ) {\n return first === second;\n }\n\n if (Object.keys(first).length !== Object.keys(second).length) {\n return false;\n }\n\n for (const key of Object.keys(first)) {\n if (!(key in second)) {\n return false;\n }\n\n if (!isEqual(first[key], second[key])) {\n return false;\n }\n }\n\n return true;\n}\n\nexport default isEqual;\n","/**\n * This implementation is taken from Lodash implementation.\n * See: https://github.com/lodash/lodash/blob/4.17.11-npm/escape.js\n */\n\n// Used to map characters to HTML entities.\nconst htmlEscapes = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n// Used to match HTML entities and HTML characters.\nconst regexUnescapedHtml = /[&<>\"']/g;\nconst regexHasUnescapedHtml = RegExp(regexUnescapedHtml.source);\n\n/**\n * Converts the characters \"&\", \"<\", \">\", '\"', and \"'\" in `string` to their\n * corresponding HTML entities.\n */\nfunction escape(value: string): string {\n return value && regexHasUnescapedHtml.test(value)\n ? value.replace(regexUnescapedHtml, character => htmlEscapes[character])\n : value;\n}\n\nexport default escape;\n","/**\n * This implementation is taken from Lodash implementation.\n * See: https://github.com/lodash/lodash/blob/4.17.11-npm/unescape.js\n */\n\n// Used to map HTML entities to characters.\nconst htmlEscapes = {\n '&': '&',\n '<': '<',\n '>': '>',\n '"': '\"',\n ''': \"'\",\n};\n\n// Used to match HTML entities and HTML characters.\nconst regexEscapedHtml = /&(amp|quot|lt|gt|#39);/g;\nconst regexHasEscapedHtml = RegExp(regexEscapedHtml.source);\n\n/**\n * Converts the HTML entities \"&\", \"<\", \">\", '\"', and \"'\" in `string` to their\n * characters.\n */\nexport default function unescape(value: string): string {\n return value && regexHasEscapedHtml.test(value)\n ? value.replace(regexEscapedHtml, character => htmlEscapes[character])\n : value;\n}\n","import escape from './escape';\nimport isPlainObject from './isPlainObject';\nimport { Hit, FacetHit, EscapedHits } from '../../types';\n\nexport const TAG_PLACEHOLDER = {\n highlightPreTag: '__ais-highlight__',\n highlightPostTag: '__/ais-highlight__',\n};\n\nexport const TAG_REPLACEMENT = {\n highlightPreTag: '',\n highlightPostTag: '',\n};\n\nfunction replaceTagsAndEscape(value: string): string {\n return escape(value)\n .replace(\n new RegExp(TAG_PLACEHOLDER.highlightPreTag, 'g'),\n TAG_REPLACEMENT.highlightPreTag\n )\n .replace(\n new RegExp(TAG_PLACEHOLDER.highlightPostTag, 'g'),\n TAG_REPLACEMENT.highlightPostTag\n );\n}\n\nfunction recursiveEscape(input: any): any {\n if (isPlainObject(input) && typeof input.value !== 'string') {\n return Object.keys(input).reduce(\n (acc, key) => ({\n ...acc,\n [key]: recursiveEscape(input[key]),\n }),\n {}\n );\n }\n\n if (Array.isArray(input)) {\n return input.map(recursiveEscape);\n }\n\n return {\n ...input,\n value: replaceTagsAndEscape(input.value),\n };\n}\n\nexport function escapeHits(hits: THit[]): EscapedHits {\n if ((hits as any).__escaped === undefined) {\n // We don't override the value on hit because it will mutate the raw results\n // instead we make a shallow copy and we assign the escaped values on it.\n hits = hits.map(({ ...hit }) => {\n if (hit._highlightResult) {\n hit._highlightResult = recursiveEscape(hit._highlightResult);\n }\n\n if (hit._snippetResult) {\n hit._snippetResult = recursiveEscape(hit._snippetResult);\n }\n\n return hit;\n });\n\n (hits as any).__escaped = true;\n }\n\n return (hits as unknown) as EscapedHits;\n}\n\nexport function escapeFacets(facetHits: FacetHit[]): FacetHit[] {\n return facetHits.map(h => ({\n ...h,\n highlighted: replaceTagsAndEscape(h.highlighted),\n }));\n}\n","import { HighlightedParts } from '../../types';\nimport { TAG_REPLACEMENT } from './escape-highlight';\n\nexport default function concatHighlightedParts(parts: HighlightedParts[]) {\n const { highlightPreTag, highlightPostTag } = TAG_REPLACEMENT;\n\n return parts\n .map(part =>\n part.isHighlighted\n ? highlightPreTag + part.value + highlightPostTag\n : part.value\n )\n .join('');\n}\n","import { TAG_REPLACEMENT } from './escape-highlight';\n\nexport default function getHighlightedParts(highlightedValue: string) {\n const { highlightPostTag, highlightPreTag } = TAG_REPLACEMENT;\n\n const splitByPreTag = highlightedValue.split(highlightPreTag);\n const firstValue = splitByPreTag.shift();\n const elements = !firstValue\n ? []\n : [{ value: firstValue, isHighlighted: false }];\n\n splitByPreTag.forEach(split => {\n const splitByPostTag = split.split(highlightPostTag);\n\n elements.push({\n value: splitByPostTag[0],\n isHighlighted: true,\n });\n\n if (splitByPostTag[1] !== '') {\n elements.push({\n value: splitByPostTag[1],\n isHighlighted: false,\n });\n }\n });\n\n return elements;\n}\n","import unescape from './unescape';\nimport { HighlightedParts } from '../../types';\n\nconst hasAlphanumeric = new RegExp(/\\w/i);\n\nexport default function getHighlightFromSiblings(\n parts: HighlightedParts[],\n i: number\n) {\n const current = parts[i];\n const isNextHighlighted = parts[i + 1]?.isHighlighted || true;\n const isPreviousHighlighted = parts[i - 1]?.isHighlighted || true;\n\n if (\n !hasAlphanumeric.test(unescape(current.value)) &&\n isPreviousHighlighted === isNextHighlighted\n ) {\n return isPreviousHighlighted;\n }\n\n return current.isHighlighted;\n}\n","import { HighlightedParts } from '../../types';\nimport getHighlightFromSiblings from './getHighlightFromSiblings';\n\nexport default function reverseHighlightedParts(parts: HighlightedParts[]) {\n if (!parts.some(part => part.isHighlighted)) {\n return parts.map(part => ({ ...part, isHighlighted: false }));\n }\n\n return parts.map((part, i) => ({\n ...part,\n isHighlighted: !getHighlightFromSiblings(parts, i),\n }));\n}\n","import { SearchParameters } from 'algoliasearch-helper';\nimport findIndex from './findIndex';\nimport uniq from './uniq';\n\ntype Merger = (\n left: SearchParameters,\n right: SearchParameters\n) => SearchParameters;\n\nconst mergeWithRest: Merger = (left, right) => {\n const {\n facets,\n disjunctiveFacets,\n facetsRefinements,\n facetsExcludes,\n disjunctiveFacetsRefinements,\n numericRefinements,\n tagRefinements,\n hierarchicalFacets,\n hierarchicalFacetsRefinements,\n ruleContexts,\n ...rest\n } = right;\n\n return left.setQueryParameters(rest);\n};\n\n// Merge facets\nconst mergeFacets: Merger = (left, right) =>\n right.facets!.reduce((_, name) => _.addFacet(name), left);\n\nconst mergeDisjunctiveFacets: Merger = (left, right) =>\n right.disjunctiveFacets!.reduce(\n (_, name) => _.addDisjunctiveFacet(name),\n left\n );\n\nconst mergeHierarchicalFacets: Merger = (left, right) =>\n left.setQueryParameters({\n hierarchicalFacets: right.hierarchicalFacets.reduce((facets, facet) => {\n const index = findIndex(facets, _ => _.name === facet.name);\n\n if (index === -1) {\n return facets.concat(facet);\n }\n\n const nextFacets = facets.slice();\n nextFacets.splice(index, 1, facet);\n\n return nextFacets;\n }, left.hierarchicalFacets),\n });\n\n// Merge facet refinements\nconst mergeTagRefinements: Merger = (left, right) =>\n right.tagRefinements!.reduce((_, value) => _.addTagRefinement(value), left);\n\nconst mergeFacetRefinements: Merger = (left, right) =>\n left.setQueryParameters({\n facetsRefinements: {\n ...left.facetsRefinements,\n ...right.facetsRefinements,\n },\n });\n\nconst mergeFacetsExcludes: Merger = (left, right) =>\n left.setQueryParameters({\n facetsExcludes: {\n ...left.facetsExcludes,\n ...right.facetsExcludes,\n },\n });\n\nconst mergeDisjunctiveFacetsRefinements: Merger = (left, right) =>\n left.setQueryParameters({\n disjunctiveFacetsRefinements: {\n ...left.disjunctiveFacetsRefinements,\n ...right.disjunctiveFacetsRefinements,\n },\n });\n\nconst mergeNumericRefinements: Merger = (left, right) =>\n left.setQueryParameters({\n numericRefinements: {\n ...left.numericRefinements,\n ...right.numericRefinements,\n },\n });\n\nconst mergeHierarchicalFacetsRefinements: Merger = (left, right) =>\n left.setQueryParameters({\n hierarchicalFacetsRefinements: {\n ...left.hierarchicalFacetsRefinements,\n ...right.hierarchicalFacetsRefinements,\n },\n });\n\nconst mergeRuleContexts: Merger = (left, right) => {\n const ruleContexts: string[] = uniq(\n ([] as any)\n .concat(left.ruleContexts)\n .concat(right.ruleContexts)\n .filter(Boolean)\n );\n\n if (ruleContexts.length > 0) {\n return left.setQueryParameters({\n ruleContexts,\n });\n }\n\n return left;\n};\n\nconst merge = (...parameters: SearchParameters[]): SearchParameters =>\n parameters.reduce((left, right) => {\n const hierarchicalFacetsRefinementsMerged = mergeHierarchicalFacetsRefinements(\n left,\n right\n );\n const hierarchicalFacetsMerged = mergeHierarchicalFacets(\n hierarchicalFacetsRefinementsMerged,\n right\n );\n const tagRefinementsMerged = mergeTagRefinements(\n hierarchicalFacetsMerged,\n right\n );\n const numericRefinementsMerged = mergeNumericRefinements(\n tagRefinementsMerged,\n right\n );\n const disjunctiveFacetsRefinementsMerged = mergeDisjunctiveFacetsRefinements(\n numericRefinementsMerged,\n right\n );\n const facetsExcludesMerged = mergeFacetsExcludes(\n disjunctiveFacetsRefinementsMerged,\n right\n );\n const facetRefinementsMerged = mergeFacetRefinements(\n facetsExcludesMerged,\n right\n );\n const disjunctiveFacetsMerged = mergeDisjunctiveFacets(\n facetRefinementsMerged,\n right\n );\n const ruleContextsMerged = mergeRuleContexts(\n disjunctiveFacetsMerged,\n right\n );\n const facetsMerged = mergeFacets(ruleContextsMerged, right);\n\n return mergeWithRest(facetsMerged, right);\n });\n\nexport default merge;\n","// We aren't using the native `Array.prototype.findIndex` because the refactor away from Lodash is not\n// published as a major version.\n// Relying on the `findIndex` polyfill on user-land, which before was only required for niche use-cases,\n// was decided as too risky.\n// @MAJOR Replace with the native `Array.prototype.findIndex` method\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex\nfunction findIndex(\n array: TItem[],\n comparator: (value: TItem) => boolean\n): number {\n if (!Array.isArray(array)) {\n return -1;\n }\n\n for (let i = 0; i < array.length; i++) {\n if (comparator(array[i])) {\n return i;\n }\n }\n return -1;\n}\n\nexport default findIndex;\n","function toArray(value: any) {\n return Array.isArray(value) ? value : [value];\n}\n\nexport default toArray;\n","type WidgetParam = {\n name: string;\n connector?: boolean;\n};\n\nexport const createDocumentationLink = ({\n name,\n connector = false,\n}: WidgetParam): string => {\n return [\n 'https://www.algolia.com/doc/api-reference/widgets/',\n name,\n '/js/',\n connector ? '#connector' : '',\n ].join('');\n};\n\ntype DocumentationMessageGenerator = (message?: string) => string;\n\nexport const createDocumentationMessageGenerator = (\n ...widgets: WidgetParam[]\n): DocumentationMessageGenerator => {\n const links = widgets\n .map(widget => createDocumentationLink(widget))\n .join(', ');\n\n return (message?: string) =>\n [message, `See documentation: ${links}`].filter(Boolean).join('\\n\\n');\n};\n","const latLngRegExp = /^(-?\\d+(?:\\.\\d+)?),\\s*(-?\\d+(?:\\.\\d+)?)$/;\n\nexport function aroundLatLngToPosition(value: string) {\n const pattern = value.match(latLngRegExp);\n\n // Since the value provided is the one send with the request, the API should\n // throw an error due to the wrong format. So throw an error should be safe.\n if (!pattern) {\n throw new Error(`Invalid value for \"aroundLatLng\" parameter: \"${value}\"`);\n }\n\n return {\n lat: parseFloat(pattern[1]),\n lng: parseFloat(pattern[2]),\n };\n}\n\ntype LatLng = Array<[number, number, number, number]>;\n\nexport function insideBoundingBoxArrayToBoundingBox(value: LatLng) {\n const [\n [neLat, neLng, swLat, swLng] = [undefined, undefined, undefined, undefined],\n ] = value;\n\n // Since the value provided is the one send with the request, the API should\n // throw an error due to the wrong format. So throw an error should be safe.\n if (!neLat || !neLng || !swLat || !swLng) {\n throw new Error(\n `Invalid value for \"insideBoundingBox\" parameter: [${value}]`\n );\n }\n\n return {\n northEast: {\n lat: neLat,\n lng: neLng,\n },\n southWest: {\n lat: swLat,\n lng: swLng,\n },\n };\n}\n\nexport function insideBoundingBoxStringToBoundingBox(value: string) {\n const [neLat, neLng, swLat, swLng] = value.split(',').map(parseFloat);\n\n // Since the value provided is the one send with the request, the API should\n // throw an error due to the wrong format. So throw an error should be safe.\n if (!neLat || !neLng || !swLat || !swLng) {\n throw new Error(\n `Invalid value for \"insideBoundingBox\" parameter: \"${value}\"`\n );\n }\n\n return {\n northEast: {\n lat: neLat,\n lng: neLng,\n },\n southWest: {\n lat: swLat,\n lng: swLng,\n },\n };\n}\n\nexport function insideBoundingBoxToBoundingBox(value: string | LatLng) {\n if (Array.isArray(value)) {\n return insideBoundingBoxArrayToBoundingBox(value);\n }\n\n return insideBoundingBoxStringToBoundingBox(value);\n}\n","import { Hits } from '../../types';\n\nexport const addAbsolutePosition = (\n hits: Hits,\n page: number,\n hitsPerPage: number\n): Hits => {\n return hits.map((hit, idx) => ({\n ...hit,\n __position: hitsPerPage * page + idx + 1,\n }));\n};\n","import { Hits } from '../../types';\n\nexport const addQueryID = (hits: Hits, queryID: string): Hits => {\n if (!queryID) {\n return hits;\n }\n return hits.map(hit => ({\n ...hit,\n __queryID: queryID,\n }));\n};\n","import { AlgoliaSearchHelper } from 'algoliasearch-helper';\nimport { InstantSearch } from '../../types';\nimport isFacetRefined from './isFacetRefined';\n\ntype BuiltInSendEventForFacet = (\n eventType: string,\n facetValue: string,\n eventName?: string\n) => void;\ntype CustomSendEventForFacet = (customPayload: any) => void;\n\nexport type SendEventForFacet = BuiltInSendEventForFacet &\n CustomSendEventForFacet;\n\nexport function createSendEventForFacet({\n instantSearchInstance,\n helper,\n attribute,\n widgetType,\n}: {\n instantSearchInstance: InstantSearch;\n helper: AlgoliaSearchHelper;\n attribute: string;\n widgetType: string;\n}): SendEventForFacet {\n const sendEventForFacet: SendEventForFacet = (...args) => {\n const [eventType, facetValue, eventName = 'Filter Applied'] = args;\n if (args.length === 1 && typeof args[0] === 'object') {\n instantSearchInstance.sendEventToInsights(args[0]);\n } else if (\n eventType === 'click' &&\n (args.length === 2 || args.length === 3)\n ) {\n if (!isFacetRefined(helper, attribute, facetValue)) {\n // send event only when the facet is being checked \"ON\"\n instantSearchInstance.sendEventToInsights({\n insightsMethod: 'clickedFilters',\n widgetType,\n eventType,\n payload: {\n eventName,\n index: helper.getIndex(),\n filters: [`${attribute}:${facetValue}`],\n },\n });\n }\n } else if (__DEV__) {\n throw new Error(\n `You need to pass two arguments like:\n sendEvent('click', facetValue);\n\nIf you want to send a custom payload, you can pass one object: sendEvent(customPayload);\n`\n );\n }\n };\n return sendEventForFacet;\n}\n","import { AlgoliaSearchHelper } from 'algoliasearch-helper';\n\nexport default function isFacetRefined(\n helper: AlgoliaSearchHelper,\n facet: string,\n value: string\n) {\n if (helper.state.isHierarchicalFacet(facet)) {\n return helper.state.isHierarchicalFacetRefined(facet, value);\n } else if (helper.state.isConjunctiveFacet(facet)) {\n return helper.state.isFacetRefined(facet, value);\n } else {\n return helper.state.isDisjunctiveFacetRefined(facet, value);\n }\n}\n","import { InstantSearch, Hit } from '../../types';\nimport { InsightsEvent } from '../../middlewares/createInsightsMiddleware';\n\ntype BuiltInSendEventForHits = (\n eventType: string,\n hits: Hit | Hit[],\n eventName?: string\n) => void;\ntype CustomSendEventForHits = (customPayload: any) => void;\nexport type SendEventForHits = BuiltInSendEventForHits & CustomSendEventForHits;\n\ntype BuiltInBindEventForHits = (\n eventType: string,\n hits: Hit | Hit[],\n eventName?: string\n) => string;\ntype CustomBindEventForHits = (customPayload: any) => string;\nexport type BindEventForHits = BuiltInBindEventForHits & CustomBindEventForHits;\n\ntype BuildPayload = (options: {\n widgetType: string;\n index: string;\n methodName: 'sendEvent' | 'bindEvent';\n args: any[];\n}) => InsightsEvent | null;\n\nconst buildPayload: BuildPayload = ({\n index,\n widgetType,\n methodName,\n args,\n}) => {\n if (args.length === 1 && typeof args[0] === 'object') {\n return args[0];\n }\n const eventType: string = args[0];\n const hits: Hit | Hit[] = args[1];\n const eventName: string | undefined = args[2];\n if (!hits) {\n if (__DEV__) {\n throw new Error(\n `You need to pass hit or hits as the second argument like:\n ${methodName}(eventType, hit);\n `\n );\n } else {\n return null;\n }\n }\n if ((eventType === 'click' || eventType === 'conversion') && !eventName) {\n if (__DEV__) {\n throw new Error(\n `You need to pass eventName as the third argument for 'click' or 'conversion' events like:\n ${methodName}('click', hit, 'Product Purchased');\n\n To learn more about event naming: https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-through-and-conversions/in-depth/clicks-conversions-best-practices/\n `\n );\n } else {\n return null;\n }\n }\n const hitsArray = Array.isArray(hits) ? hits : [hits];\n if (hitsArray.length === 0) {\n return null;\n }\n const queryID = hitsArray[0].__queryID;\n const objectIDs = hitsArray.map(hit => hit.objectID);\n const positions = hitsArray.map(hit => hit.__position);\n\n if (eventType === 'view') {\n return {\n insightsMethod: 'viewedObjectIDs',\n widgetType,\n eventType,\n payload: {\n eventName: eventName || 'Hits Viewed',\n index,\n objectIDs,\n },\n };\n } else if (eventType === 'click') {\n return {\n insightsMethod: 'clickedObjectIDsAfterSearch',\n widgetType,\n eventType,\n payload: {\n eventName,\n index,\n queryID,\n objectIDs,\n positions,\n },\n };\n } else if (eventType === 'conversion') {\n return {\n insightsMethod: 'convertedObjectIDsAfterSearch',\n widgetType,\n eventType,\n payload: {\n eventName,\n index,\n queryID,\n objectIDs,\n },\n };\n } else if (__DEV__) {\n throw new Error(`eventType(\"${eventType}\") is not supported.\n If you want to send a custom payload, you can pass one object: ${methodName}(customPayload);\n `);\n } else {\n return null;\n }\n};\n\nexport function createSendEventForHits({\n instantSearchInstance,\n index,\n widgetType,\n}: {\n instantSearchInstance: InstantSearch;\n index: string;\n widgetType: string;\n}): SendEventForHits {\n const sendEventForHits: SendEventForHits = (...args) => {\n const payload = buildPayload({\n widgetType,\n index,\n methodName: 'sendEvent',\n args,\n });\n if (payload) {\n instantSearchInstance.sendEventToInsights(payload);\n }\n };\n return sendEventForHits;\n}\n\nexport function createBindEventForHits({\n index,\n widgetType,\n}: {\n index: string;\n widgetType: string;\n}): BindEventForHits {\n const bindEventForHits: BindEventForHits = (...args) => {\n const payload = buildPayload({\n widgetType,\n index,\n methodName: 'bindEvent',\n args,\n });\n return payload\n ? `data-insights-event=${btoa(JSON.stringify(payload))}`\n : '';\n };\n return bindEventForHits;\n}\n","import { SearchParameters } from 'algoliasearch-helper';\n\nexport function convertNumericRefinementsToFilters(\n state: SearchParameters | null,\n attribute: string\n) {\n if (!state) {\n return null;\n }\n const filtersObj = state.numericRefinements[attribute];\n /*\n filtersObj === {\n \"<=\": [10],\n \"=\": [],\n \">=\": [5]\n }\n */\n const filters: string[] = [];\n Object.keys(filtersObj)\n .filter(\n operator =>\n Array.isArray(filtersObj[operator]) && filtersObj[operator].length > 0\n )\n .forEach(operator => {\n filtersObj[operator].forEach(value => {\n filters.push(`${attribute}${operator}${value}`);\n });\n });\n return filters;\n}\n","import algoliasearchHelper, {\n AlgoliaSearchHelper as Helper,\n DerivedHelper,\n PlainSearchParameters,\n SearchParameters,\n SearchResults,\n AlgoliaSearchHelper,\n} from 'algoliasearch-helper';\nimport {\n InstantSearch,\n UiState,\n IndexUiState,\n Widget,\n InitOptions,\n RenderOptions,\n WidgetUiStateOptions,\n WidgetSearchParametersOptions,\n ScopedResult,\n SearchClient,\n} from '../../types';\nimport {\n checkIndexUiState,\n createDocumentationMessageGenerator,\n resolveSearchParameters,\n mergeSearchParameters,\n warning,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'index-widget',\n});\n\ntype IndexProps = {\n indexName: string;\n indexId?: string;\n};\n\ntype IndexInitOptions = Pick<\n InitOptions,\n 'instantSearchInstance' | 'parent' | 'uiState'\n>;\n\ntype IndexRenderOptions = Pick;\n\ntype LocalWidgetSearchParametersOptions = WidgetSearchParametersOptions & {\n initialSearchParameters: SearchParameters;\n};\n\nexport type Index = Widget & {\n getIndexName(): string;\n getIndexId(): string;\n getHelper(): Helper | null;\n getResults(): SearchResults | null;\n getScopedResults(): ScopedResult[];\n getParent(): Index | null;\n getWidgets(): Widget[];\n createURL(state: SearchParameters): string;\n\n addWidgets(widgets: Widget[]): Index;\n removeWidgets(widgets: Widget[]): Index;\n\n init(options: IndexInitOptions): void;\n render(options: IndexRenderOptions): void;\n dispose(): void;\n /**\n * @deprecated\n */\n getWidgetState(uiState: UiState): UiState;\n getWidgetUiState(uiState: UiState): UiState;\n getWidgetSearchParameters(\n searchParameters: SearchParameters,\n searchParametersOptions: { uiState: IndexUiState }\n ): SearchParameters;\n refreshUiState(): void;\n};\n\nexport function isIndexWidget(widget: Widget): widget is Index {\n return widget.$$type === 'ais.index';\n}\n\n/**\n * This is the same content as helper._change / setState, but allowing for extra\n * UiState to be synchronized.\n * see: https://github.com/algolia/algoliasearch-helper-js/blob/6b835ffd07742f2d6b314022cce6848f5cfecd4a/src/algoliasearch.helper.js#L1311-L1324\n */\nfunction privateHelperSetState(\n helper: AlgoliaSearchHelper,\n {\n state,\n isPageReset,\n _uiState,\n }: {\n state: SearchParameters;\n isPageReset?: boolean;\n _uiState?: IndexUiState;\n }\n) {\n if (state !== helper.state) {\n helper.state = state;\n\n helper.emit('change', {\n state: helper.state,\n results: helper.lastResults,\n isPageReset,\n _uiState,\n });\n }\n}\n\nfunction getLocalWidgetsUiState(\n widgets: Widget[],\n widgetStateOptions: WidgetUiStateOptions,\n initialUiState: IndexUiState = {}\n): IndexUiState {\n return widgets\n .filter(widget => !isIndexWidget(widget))\n .reduce((uiState, widget) => {\n if (!widget.getWidgetUiState && !widget.getWidgetState) {\n return uiState;\n }\n\n if (widget.getWidgetUiState) {\n return widget.getWidgetUiState(uiState, widgetStateOptions);\n }\n\n return widget.getWidgetState!(uiState, widgetStateOptions);\n }, initialUiState);\n}\n\nfunction getLocalWidgetsSearchParameters(\n widgets: Widget[],\n widgetSearchParametersOptions: LocalWidgetSearchParametersOptions\n): SearchParameters {\n const { initialSearchParameters, ...rest } = widgetSearchParametersOptions;\n\n return widgets\n .filter(widget => !isIndexWidget(widget))\n .reduce((state, widget) => {\n if (!widget.getWidgetSearchParameters) {\n return state;\n }\n\n return widget.getWidgetSearchParameters(state, rest);\n }, initialSearchParameters);\n}\n\nfunction resetPageFromWidgets(widgets: Widget[]): void {\n const indexWidgets = widgets.filter(isIndexWidget);\n\n if (indexWidgets.length === 0) {\n return;\n }\n\n indexWidgets.forEach(widget => {\n const widgetHelper = widget.getHelper()!;\n\n privateHelperSetState(widgetHelper, {\n state: widgetHelper.state.resetPage(),\n isPageReset: true,\n });\n\n resetPageFromWidgets(widget.getWidgets());\n });\n}\n\nfunction resolveScopedResultsFromWidgets(widgets: Widget[]): ScopedResult[] {\n const indexWidgets = widgets.filter(isIndexWidget);\n\n return indexWidgets.reduce((scopedResults, current) => {\n return scopedResults.concat(\n {\n indexId: current.getIndexId(),\n results: current.getResults()!,\n helper: current.getHelper()!,\n },\n ...resolveScopedResultsFromWidgets(current.getWidgets())\n );\n }, []);\n}\n\nconst index = (props: IndexProps): Index => {\n if (props === undefined || props.indexName === undefined) {\n throw new Error(withUsage('The `indexName` option is required.'));\n }\n\n const { indexName, indexId = indexName } = props;\n\n let localWidgets: Widget[] = [];\n let localUiState: IndexUiState = {};\n let localInstantSearchInstance: InstantSearch | null = null;\n let localParent: Index | null = null;\n let helper: Helper | null = null;\n let derivedHelper: DerivedHelper | null = null;\n\n return {\n $$type: 'ais.index',\n $$widgetType: 'ais.index',\n\n getIndexName() {\n return indexName;\n },\n\n getIndexId() {\n return indexId;\n },\n\n getHelper() {\n return helper;\n },\n\n getResults() {\n return derivedHelper && derivedHelper.lastResults;\n },\n\n getScopedResults() {\n const widgetParent = this.getParent();\n\n // If the widget is the root, we consider itself as the only sibling.\n const widgetSiblings = widgetParent ? widgetParent.getWidgets() : [this];\n\n return resolveScopedResultsFromWidgets(widgetSiblings);\n },\n\n getParent() {\n return localParent;\n },\n\n createURL(nextState: SearchParameters) {\n return localInstantSearchInstance!._createURL!({\n [indexId]: getLocalWidgetsUiState(localWidgets, {\n searchParameters: nextState,\n helper: helper!,\n }),\n });\n },\n\n getWidgets() {\n return localWidgets;\n },\n\n addWidgets(widgets) {\n if (!Array.isArray(widgets)) {\n throw new Error(\n withUsage('The `addWidgets` method expects an array of widgets.')\n );\n }\n\n if (\n widgets.some(\n widget =>\n typeof widget.init !== 'function' &&\n typeof widget.render !== 'function'\n )\n ) {\n throw new Error(\n withUsage(\n 'The widget definition expects a `render` and/or an `init` method.'\n )\n );\n }\n\n localWidgets = localWidgets.concat(widgets);\n\n if (localInstantSearchInstance && Boolean(widgets.length)) {\n privateHelperSetState(helper!, {\n state: getLocalWidgetsSearchParameters(localWidgets, {\n uiState: localUiState,\n initialSearchParameters: helper!.state,\n }),\n _uiState: localUiState,\n });\n\n // We compute the render state before calling `init` in a separate loop\n // to construct the whole render state object that is then passed to\n // `init`.\n widgets.forEach(widget => {\n if (widget.getRenderState) {\n const renderState = widget.getRenderState(\n localInstantSearchInstance!.renderState[this.getIndexId()] || {},\n {\n uiState: localInstantSearchInstance!._initialUiState,\n helper: this.getHelper()!,\n parent: this,\n instantSearchInstance: localInstantSearchInstance!,\n state: helper!.state,\n renderState: localInstantSearchInstance!.renderState,\n templatesConfig: localInstantSearchInstance!.templatesConfig,\n createURL: this.createURL,\n scopedResults: [],\n searchMetadata: {\n isSearchStalled: localInstantSearchInstance!._isSearchStalled,\n },\n }\n );\n\n storeRenderState({\n renderState,\n instantSearchInstance: localInstantSearchInstance!,\n parent: this,\n });\n }\n });\n\n widgets.forEach(widget => {\n if (widget.init) {\n widget.init({\n helper: helper!,\n parent: this,\n uiState: localInstantSearchInstance!._initialUiState,\n instantSearchInstance: localInstantSearchInstance!,\n state: helper!.state,\n renderState: localInstantSearchInstance!.renderState,\n templatesConfig: localInstantSearchInstance!.templatesConfig,\n createURL: this.createURL,\n scopedResults: [],\n searchMetadata: {\n isSearchStalled: localInstantSearchInstance!._isSearchStalled,\n },\n });\n }\n });\n\n localInstantSearchInstance.scheduleSearch();\n }\n\n return this;\n },\n\n removeWidgets(widgets) {\n if (!Array.isArray(widgets)) {\n throw new Error(\n withUsage('The `removeWidgets` method expects an array of widgets.')\n );\n }\n\n if (widgets.some(widget => typeof widget.dispose !== 'function')) {\n throw new Error(\n withUsage('The widget definition expects a `dispose` method.')\n );\n }\n\n localWidgets = localWidgets.filter(\n widget => widgets.indexOf(widget) === -1\n );\n\n if (localInstantSearchInstance && Boolean(widgets.length)) {\n const nextState = widgets.reduce((state, widget) => {\n // the `dispose` method exists at this point we already assert it\n const next = widget.dispose!({ helper: helper!, state });\n\n return next || state;\n }, helper!.state);\n\n localUiState = getLocalWidgetsUiState(localWidgets, {\n searchParameters: nextState,\n helper: helper!,\n });\n\n helper!.setState(\n getLocalWidgetsSearchParameters(localWidgets, {\n uiState: localUiState,\n initialSearchParameters: nextState,\n })\n );\n\n if (localWidgets.length) {\n localInstantSearchInstance.scheduleSearch();\n }\n }\n\n return this;\n },\n\n init({ instantSearchInstance, parent, uiState }: IndexInitOptions) {\n if (helper !== null) {\n // helper is already initialized, therefore we do not need to set up\n // any listeners\n return;\n }\n\n localInstantSearchInstance = instantSearchInstance;\n localParent = parent;\n localUiState = uiState[indexId] || {};\n\n // The `mainHelper` is already defined at this point. The instance is created\n // inside InstantSearch at the `start` method, which occurs before the `init`\n // step.\n const mainHelper = instantSearchInstance.mainHelper!;\n const parameters = getLocalWidgetsSearchParameters(localWidgets, {\n uiState: localUiState,\n initialSearchParameters: new algoliasearchHelper.SearchParameters({\n index: indexName,\n }),\n });\n\n // This Helper is only used for state management we do not care about the\n // `searchClient`. Only the \"main\" Helper created at the `InstantSearch`\n // level is aware of the client.\n helper = algoliasearchHelper(\n {} as SearchClient,\n parameters.index,\n parameters\n );\n\n // We forward the call to `search` to the \"main\" instance of the Helper\n // which is responsible for managing the queries (it's the only one that is\n // aware of the `searchClient`).\n helper.search = () => {\n if (instantSearchInstance.onStateChange) {\n instantSearchInstance.onStateChange!({\n uiState: instantSearchInstance.mainIndex.getWidgetUiState({}),\n setUiState: instantSearchInstance.setUiState.bind(\n instantSearchInstance\n ),\n });\n\n // We don't trigger a search when controlled because it becomes the\n // responsibility of `setUiState`.\n return mainHelper;\n }\n\n return mainHelper.search();\n };\n\n helper.searchWithoutTriggeringOnStateChange = () => {\n return mainHelper.search();\n };\n\n // We use the same pattern for the `searchForFacetValues`.\n helper.searchForFacetValues = (\n facetName,\n facetValue,\n maxFacetHits,\n userState: PlainSearchParameters\n ) => {\n const state = helper!.state.setQueryParameters(userState);\n\n return mainHelper.searchForFacetValues(\n facetName,\n facetValue,\n maxFacetHits,\n state\n );\n };\n\n derivedHelper = mainHelper.derive(() =>\n mergeSearchParameters(...resolveSearchParameters(this))\n );\n\n // Subscribe to the Helper state changes for the page before widgets\n // are initialized. This behavior mimics the original one of the Helper.\n // It makes sense to replicate it at the `init` step. We have another\n // listener on `change` below, once `init` is done.\n helper.on('change', ({ isPageReset }) => {\n if (isPageReset) {\n resetPageFromWidgets(localWidgets);\n }\n });\n\n derivedHelper.on('search', () => {\n // The index does not manage the \"staleness\" of the search. This is the\n // responsibility of the main instance. It does not make sense to manage\n // it at the index level because it's either: all of them or none of them\n // that are stalled. The queries are performed into a single network request.\n instantSearchInstance.scheduleStalledRender();\n\n if (__DEV__) {\n checkIndexUiState({ index: this, indexUiState: localUiState });\n }\n });\n\n derivedHelper.on('result', ({ results }) => {\n // The index does not render the results it schedules a new render\n // to let all the other indices emit their own results. It allows us to\n // run the render process in one pass.\n instantSearchInstance.scheduleRender();\n\n // the derived helper is the one which actually searches, but the helper\n // which is exposed e.g. via instance.helper, doesn't search, and thus\n // does not have access to lastResults, which it used to in pre-federated\n // search behavior.\n helper!.lastResults = results;\n });\n\n // We compute the render state before calling `render` in a separate loop\n // to construct the whole render state object that is then passed to\n // `render`.\n localWidgets.forEach(widget => {\n if (widget.getRenderState) {\n const renderState = widget.getRenderState(\n instantSearchInstance.renderState[this.getIndexId()] || {},\n {\n uiState,\n helper: helper!,\n parent: this,\n instantSearchInstance,\n state: helper!.state,\n renderState: instantSearchInstance.renderState,\n templatesConfig: instantSearchInstance.templatesConfig,\n createURL: this.createURL,\n scopedResults: [],\n searchMetadata: {\n isSearchStalled: instantSearchInstance._isSearchStalled,\n },\n }\n );\n\n storeRenderState({\n renderState,\n instantSearchInstance,\n parent: this,\n });\n }\n });\n\n localWidgets.forEach(widget => {\n warning(\n // if it has NO getWidgetState or if it has getWidgetUiState, we don't warn\n // aka we warn if there's _only_ getWidgetState\n !widget.getWidgetState || Boolean(widget.getWidgetUiState),\n 'The `getWidgetState` method is renamed `getWidgetUiState` and will no longer exist under that name in InstantSearch.js 5.x. Please use `getWidgetUiState` instead.'\n );\n\n if (widget.init) {\n widget.init({\n uiState,\n helper: helper!,\n parent: this,\n instantSearchInstance,\n state: helper!.state,\n renderState: instantSearchInstance.renderState,\n templatesConfig: instantSearchInstance.templatesConfig,\n createURL: this.createURL,\n scopedResults: [],\n searchMetadata: {\n isSearchStalled: instantSearchInstance._isSearchStalled,\n },\n });\n }\n });\n\n // Subscribe to the Helper state changes for the `uiState` once widgets\n // are initialized. Until the first render, state changes are part of the\n // configuration step. This is mainly for backward compatibility with custom\n // widgets. When the subscription happens before the `init` step, the (static)\n // configuration of the widget is pushed in the URL. That's what we want to avoid.\n // https://github.com/algolia/instantsearch.js/pull/994/commits/4a672ae3fd78809e213de0368549ef12e9dc9454\n helper.on('change', event => {\n const { state } = event;\n\n // @ts-ignore _uiState comes from privateHelperSetState and thus isn't typed on the helper event\n const _uiState = event._uiState;\n\n localUiState = getLocalWidgetsUiState(\n localWidgets,\n {\n searchParameters: state,\n helper: helper!,\n },\n _uiState || {}\n );\n\n // We don't trigger an internal change when controlled because it\n // becomes the responsibility of `setUiState`.\n if (!instantSearchInstance.onStateChange) {\n instantSearchInstance.onInternalStateChange();\n }\n });\n },\n\n render({ instantSearchInstance }: IndexRenderOptions) {\n if (!this.getResults()) {\n return;\n }\n\n localWidgets.forEach(widget => {\n if (widget.getRenderState) {\n const renderState = widget.getRenderState(\n instantSearchInstance.renderState[this.getIndexId()] || {},\n {\n helper: this.getHelper()!,\n parent: this,\n instantSearchInstance,\n results: this.getResults()!,\n scopedResults: this.getScopedResults(),\n state: this.getResults()!._state,\n renderState: instantSearchInstance.renderState,\n templatesConfig: instantSearchInstance.templatesConfig,\n createURL: this.createURL,\n searchMetadata: {\n isSearchStalled: instantSearchInstance._isSearchStalled,\n },\n }\n );\n\n storeRenderState({\n renderState,\n instantSearchInstance,\n parent: this,\n });\n }\n });\n\n localWidgets.forEach(widget => {\n // At this point, all the variables used below are set. Both `helper`\n // and `derivedHelper` have been created at the `init` step. The attribute\n // `lastResults` might be `null` though. It's possible that a stalled render\n // happens before the result e.g with a dynamically added index the request might\n // be delayed. The render is triggered for the complete tree but some parts do\n // not have results yet.\n\n if (widget.render) {\n widget.render({\n helper: helper!,\n parent: this,\n instantSearchInstance,\n results: this.getResults()!,\n scopedResults: this.getScopedResults(),\n state: this.getResults()!._state,\n renderState: instantSearchInstance.renderState,\n templatesConfig: instantSearchInstance.templatesConfig,\n createURL: this.createURL,\n searchMetadata: {\n isSearchStalled: instantSearchInstance._isSearchStalled,\n },\n });\n }\n });\n },\n\n dispose() {\n localWidgets.forEach(widget => {\n if (widget.dispose) {\n // The dispose function is always called once the instance is started\n // (it's an effect of `removeWidgets`). The index is initialized and\n // the Helper is available. We don't care about the return value of\n // `dispose` because the index is removed. We can't call `removeWidgets`\n // because we want to keep the widgets on the instance, to allow idempotent\n // operations on `add` & `remove`.\n widget.dispose({ helper: helper!, state: helper!.state });\n }\n });\n\n localInstantSearchInstance = null;\n localParent = null;\n helper!.removeAllListeners();\n helper = null;\n\n derivedHelper!.detach();\n derivedHelper = null;\n },\n\n getWidgetUiState(uiState: UiState) {\n return localWidgets\n .filter(isIndexWidget)\n .reduce(\n (previousUiState, innerIndex) =>\n innerIndex.getWidgetUiState(previousUiState),\n {\n ...uiState,\n [this.getIndexId()]: localUiState,\n }\n );\n },\n\n getWidgetState(uiState: UiState) {\n warning(\n false,\n 'The `getWidgetState` method is renamed `getWidgetUiState` and will no longer exist under that name in InstantSearch.js 5.x. Please use `getWidgetUiState` instead.'\n );\n\n return this.getWidgetUiState(uiState);\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n return getLocalWidgetsSearchParameters(localWidgets, {\n uiState,\n initialSearchParameters: searchParameters,\n });\n },\n\n refreshUiState() {\n localUiState = getLocalWidgetsUiState(localWidgets, {\n searchParameters: this.getHelper()!.state,\n helper: this.getHelper()!,\n });\n },\n };\n};\n\nexport default index;\n\nfunction storeRenderState({ renderState, instantSearchInstance, parent }) {\n const parentIndexName = parent\n ? parent.getIndexId()\n : instantSearchInstance.mainIndex.getIndexId();\n\n instantSearchInstance.renderState = {\n ...instantSearchInstance.renderState,\n [parentIndexName]: {\n ...instantSearchInstance.renderState[parentIndexName],\n ...renderState,\n },\n };\n}\n","import { SearchParameters } from 'algoliasearch-helper';\nimport { Index } from '../../widgets/index/index';\n\nconst resolveSearchParameters = (current: Index): SearchParameters[] => {\n let parent = current.getParent();\n let states = [current.getHelper()!.state];\n\n while (parent !== null) {\n states = [parent.getHelper()!.state].concat(states);\n parent = parent.getParent();\n }\n\n return states;\n};\n\nexport default resolveSearchParameters;\n","const NAMESPACE = 'ais';\n\ntype SuitOptions = {\n descendantName?: string;\n modifierName?: string;\n};\n\ntype SuitSelector = (names?: SuitOptions) => string;\n\nexport const component = (componentName: string): SuitSelector => ({\n descendantName,\n modifierName,\n}: SuitOptions = {}) => {\n const descendent = descendantName ? `-${descendantName}` : '';\n const modifier = modifierName ? `--${modifierName}` : '';\n\n return `${NAMESPACE}-${componentName}${descendent}${modifier}`;\n};\n","import { Hit } from '../types';\nimport { component } from '../lib/suit';\nimport { getPropertyByPath, TAG_REPLACEMENT } from '../lib/utils';\n\nexport type HighlightOptions = {\n // @MAJOR string should no longer be allowed to be a path, only array can be a path\n attribute: string | string[];\n highlightedTagName?: string;\n hit: Partial;\n cssClasses?: Partial<{\n highlighted: string;\n }>;\n};\n\nconst suit = component('Highlight');\n\nexport default function highlight({\n attribute,\n highlightedTagName = 'mark',\n hit,\n cssClasses = {},\n}: HighlightOptions): string {\n const { value: attributeValue = '' } =\n getPropertyByPath(hit._highlightResult, attribute) || {};\n\n // cx is not used, since it would be bundled as a dependency for Vue & Angular\n const className =\n suit({\n descendantName: 'highlighted',\n }) + (cssClasses.highlighted ? ` ${cssClasses.highlighted}` : '');\n\n return attributeValue\n .replace(\n new RegExp(TAG_REPLACEMENT.highlightPreTag, 'g'),\n `<${highlightedTagName} class=\"${className}\">`\n )\n .replace(\n new RegExp(TAG_REPLACEMENT.highlightPostTag, 'g'),\n ``\n );\n}\n","import { Hit } from '../types';\nimport {\n TAG_REPLACEMENT,\n getPropertyByPath,\n getHighlightedParts,\n reverseHighlightedParts,\n concatHighlightedParts,\n} from '../lib/utils';\nimport { component } from '../lib/suit';\n\nexport type ReverseHighlightOptions = {\n // @MAJOR string should no longer be allowed to be a path, only array can be a path\n attribute: string | string[];\n highlightedTagName?: string;\n hit: Partial;\n cssClasses?: Partial<{\n highlighted: string;\n }>;\n};\n\nconst suit = component('ReverseHighlight');\n\nexport default function reverseHighlight({\n attribute,\n highlightedTagName = 'mark',\n hit,\n cssClasses = {},\n}: ReverseHighlightOptions): string {\n const { value: attributeValue = '' } =\n getPropertyByPath(hit._highlightResult, attribute) || {};\n\n // cx is not used, since it would be bundled as a dependency for Vue & Angular\n const className =\n suit({\n descendantName: 'highlighted',\n }) + (cssClasses.highlighted ? ` ${cssClasses.highlighted}` : '');\n\n const reverseHighlightedValue = concatHighlightedParts(\n reverseHighlightedParts(getHighlightedParts(attributeValue))\n );\n\n return reverseHighlightedValue\n .replace(\n new RegExp(TAG_REPLACEMENT.highlightPreTag, 'g'),\n `<${highlightedTagName} class=\"${className}\">`\n )\n .replace(\n new RegExp(TAG_REPLACEMENT.highlightPostTag, 'g'),\n ``\n );\n}\n","import { Hit } from '../types';\nimport { component } from '../lib/suit';\nimport { TAG_REPLACEMENT, getPropertyByPath } from '../lib/utils';\n\nexport type SnippetOptions = {\n // @MAJOR string should no longer be allowed to be a path, only array can be a path\n attribute: string | string[];\n highlightedTagName?: string;\n hit: Partial;\n cssClasses?: {\n highlighted?: string;\n };\n};\n\nconst suit = component('Snippet');\n\nexport default function snippet({\n attribute,\n highlightedTagName = 'mark',\n hit,\n cssClasses = {},\n}: SnippetOptions): string {\n const { value: attributeValue = '' } =\n getPropertyByPath(hit._snippetResult, attribute) || {};\n\n // cx is not used, since it would be bundled as a dependency for Vue & Angular\n const className =\n suit({\n descendantName: 'highlighted',\n }) + (cssClasses.highlighted ? ` ${cssClasses.highlighted}` : '');\n\n return attributeValue\n .replace(\n new RegExp(TAG_REPLACEMENT.highlightPreTag, 'g'),\n `<${highlightedTagName} class=\"${className}\">`\n )\n .replace(\n new RegExp(TAG_REPLACEMENT.highlightPostTag, 'g'),\n ``\n );\n}\n","import { Hit } from '../types';\nimport {\n TAG_REPLACEMENT,\n getPropertyByPath,\n getHighlightedParts,\n reverseHighlightedParts,\n concatHighlightedParts,\n} from '../lib/utils';\nimport { component } from '../lib/suit';\n\nexport type ReverseSnippetOptions = {\n // @MAJOR string should no longer be allowed to be a path, only array can be a path\n attribute: string | string[];\n highlightedTagName?: string;\n hit: Partial;\n cssClasses?: Partial<{\n highlighted: string;\n }>;\n};\n\nconst suit = component('ReverseSnippet');\n\nexport default function reverseSnippet({\n attribute,\n highlightedTagName = 'mark',\n hit,\n cssClasses = {},\n}: ReverseSnippetOptions): string {\n const { value: attributeValue = '' } =\n getPropertyByPath(hit._snippetResult, attribute) || {};\n\n // cx is not used, since it would be bundled as a dependency for Vue & Angular\n const className =\n suit({\n descendantName: 'highlighted',\n }) + (cssClasses.highlighted ? ` ${cssClasses.highlighted}` : '');\n\n const reverseHighlightedValue = concatHighlightedParts(\n reverseHighlightedParts(getHighlightedParts(attributeValue))\n );\n\n return reverseHighlightedValue\n .replace(\n new RegExp(TAG_REPLACEMENT.highlightPreTag, 'g'),\n `<${highlightedTagName} class=\"${className}\">`\n )\n .replace(\n new RegExp(TAG_REPLACEMENT.highlightPostTag, 'g'),\n ``\n );\n}\n","import { InsightsClientMethod, InsightsClientPayload } from '../types';\nimport { warning } from '../lib/utils';\n\nexport function readDataAttributes(\n domElement: HTMLElement\n): {\n method: InsightsClientMethod;\n payload: Partial;\n} {\n const method = domElement.getAttribute(\n 'data-insights-method'\n ) as InsightsClientMethod;\n\n const serializedPayload = domElement.getAttribute('data-insights-payload');\n\n if (typeof serializedPayload !== 'string') {\n throw new Error(\n 'The insights helper expects `data-insights-payload` to be a base64-encoded JSON string.'\n );\n }\n\n try {\n const payload: Partial = JSON.parse(\n atob(serializedPayload)\n );\n return { method, payload };\n } catch (error) {\n throw new Error(\n 'The insights helper was unable to parse `data-insights-payload`.'\n );\n }\n}\n\nexport function hasDataAttributes(domElement: HTMLElement): boolean {\n return domElement.hasAttribute('data-insights-method');\n}\n\nexport function writeDataAttributes({\n method,\n payload,\n}: {\n method: InsightsClientMethod;\n payload: Partial;\n}): string {\n if (typeof payload !== 'object') {\n throw new Error(`The insights helper expects the payload to be an object.`);\n }\n\n let serializedPayload: string;\n\n try {\n serializedPayload = btoa(JSON.stringify(payload));\n } catch (error) {\n throw new Error(`Could not JSON serialize the payload object.`);\n }\n\n return `data-insights-method=\"${method}\" data-insights-payload=\"${serializedPayload}\"`;\n}\n\n/**\n * @deprecated This function will be still supported in 4.x releases, but not further. It is replaced by the `insights` middleware. For more information, visit https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-through-and-conversions/how-to/send-click-and-conversion-events-with-instantsearch/js/\n */\nexport default function insights(\n method: InsightsClientMethod,\n payload: Partial\n): string {\n warning(\n false,\n `\\`insights\\` function has been deprecated. It is still supported in 4.x releases, but not further. It is replaced by the \\`insights\\` middleware.\n\nFor more information, visit https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-through-and-conversions/how-to/send-click-and-conversion-events-with-instantsearch/js/`\n );\n return writeDataAttributes({ method, payload });\n}\n","import { warning } from '../lib/utils';\n\nexport const ANONYMOUS_TOKEN_COOKIE_KEY = '_ALGOLIA';\n\nfunction getCookie(name: string): string | undefined {\n const prefix = `${name}=`;\n const cookies = document.cookie.split(';');\n for (let i = 0; i < cookies.length; i++) {\n let cookie = cookies[i];\n while (cookie.charAt(0) === ' ') {\n cookie = cookie.substring(1);\n }\n if (cookie.indexOf(prefix) === 0) {\n return cookie.substring(prefix.length, cookie.length);\n }\n }\n return undefined;\n}\n\nexport function getInsightsAnonymousUserTokenInternal(): string | undefined {\n return getCookie(ANONYMOUS_TOKEN_COOKIE_KEY);\n}\n\n/**\n * @deprecated This function will be still supported in 4.x releases, but not further. It is replaced by the `insights` middleware. For more information, visit https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-through-and-conversions/how-to/send-click-and-conversion-events-with-instantsearch/js/\n */\nexport default function getInsightsAnonymousUserToken(): string | undefined {\n warning(\n false,\n `\\`getInsightsAnonymousUserToken\\` function has been deprecated. It is still supported in 4.x releases, but not further. It is replaced by the \\`insights\\` middleware.\n\nFor more information, visit https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-through-and-conversions/how-to/send-click-and-conversion-events-with-instantsearch/js/`\n );\n return getInsightsAnonymousUserTokenInternal();\n}\n","import { UiState, IndexUiState, StateMapping, RouteState } from '../../types';\n\nfunction getIndexStateWithoutConfigure(uiState: IndexUiState): IndexUiState {\n const { configure, ...trackedUiState } = uiState;\n return trackedUiState;\n}\n\n// technically a URL could contain any key, since users provide it,\n// which is why the input to this function is UiState, not something\n// which excludes \"configure\" as this function does.\nexport default function simpleStateMapping(): StateMapping {\n return {\n stateToRoute(uiState) {\n return Object.keys(uiState).reduce(\n (state, indexId) => ({\n ...state,\n [indexId]: getIndexStateWithoutConfigure(uiState[indexId]),\n }),\n {}\n );\n },\n\n routeToState(routeState = {}) {\n return Object.keys(routeState).reduce(\n (state, indexId) => ({\n ...state,\n [indexId]: getIndexStateWithoutConfigure(routeState[indexId]),\n }),\n {}\n );\n },\n };\n}\n","'use strict';\n\nvar has = Object.prototype.hasOwnProperty;\nvar isArray = Array.isArray;\n\nvar hexTable = (function () {\n var array = [];\n for (var i = 0; i < 256; ++i) {\n array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase());\n }\n\n return array;\n}());\n\nvar compactQueue = function compactQueue(queue) {\n while (queue.length > 1) {\n var item = queue.pop();\n var obj = item.obj[item.prop];\n\n if (isArray(obj)) {\n var compacted = [];\n\n for (var j = 0; j < obj.length; ++j) {\n if (typeof obj[j] !== 'undefined') {\n compacted.push(obj[j]);\n }\n }\n\n item.obj[item.prop] = compacted;\n }\n }\n};\n\nvar arrayToObject = function arrayToObject(source, options) {\n var obj = options && options.plainObjects ? Object.create(null) : {};\n for (var i = 0; i < source.length; ++i) {\n if (typeof source[i] !== 'undefined') {\n obj[i] = source[i];\n }\n }\n\n return obj;\n};\n\nvar merge = function merge(target, source, options) {\n if (!source) {\n return target;\n }\n\n if (typeof source !== 'object') {\n if (isArray(target)) {\n target.push(source);\n } else if (target && typeof target === 'object') {\n if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {\n target[source] = true;\n }\n } else {\n return [target, source];\n }\n\n return target;\n }\n\n if (!target || typeof target !== 'object') {\n return [target].concat(source);\n }\n\n var mergeTarget = target;\n if (isArray(target) && !isArray(source)) {\n mergeTarget = arrayToObject(target, options);\n }\n\n if (isArray(target) && isArray(source)) {\n source.forEach(function (item, i) {\n if (has.call(target, i)) {\n var targetItem = target[i];\n if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {\n target[i] = merge(targetItem, item, options);\n } else {\n target.push(item);\n }\n } else {\n target[i] = item;\n }\n });\n return target;\n }\n\n return Object.keys(source).reduce(function (acc, key) {\n var value = source[key];\n\n if (has.call(acc, key)) {\n acc[key] = merge(acc[key], value, options);\n } else {\n acc[key] = value;\n }\n return acc;\n }, mergeTarget);\n};\n\nvar assign = function assignSingleSource(target, source) {\n return Object.keys(source).reduce(function (acc, key) {\n acc[key] = source[key];\n return acc;\n }, target);\n};\n\nvar decode = function (str, decoder, charset) {\n var strWithoutPlus = str.replace(/\\+/g, ' ');\n if (charset === 'iso-8859-1') {\n // unescape never throws, no try...catch needed:\n return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);\n }\n // utf-8\n try {\n return decodeURIComponent(strWithoutPlus);\n } catch (e) {\n return strWithoutPlus;\n }\n};\n\nvar encode = function encode(str, defaultEncoder, charset) {\n // This code was originally written by Brian White (mscdex) for the io.js core querystring library.\n // It has been adapted here for stricter adherence to RFC 3986\n if (str.length === 0) {\n return str;\n }\n\n var string = str;\n if (typeof str === 'symbol') {\n string = Symbol.prototype.toString.call(str);\n } else if (typeof str !== 'string') {\n string = String(str);\n }\n\n if (charset === 'iso-8859-1') {\n return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) {\n return '%26%23' + parseInt($0.slice(2), 16) + '%3B';\n });\n }\n\n var out = '';\n for (var i = 0; i < string.length; ++i) {\n var c = string.charCodeAt(i);\n\n if (\n c === 0x2D // -\n || c === 0x2E // .\n || c === 0x5F // _\n || c === 0x7E // ~\n || (c >= 0x30 && c <= 0x39) // 0-9\n || (c >= 0x41 && c <= 0x5A) // a-z\n || (c >= 0x61 && c <= 0x7A) // A-Z\n ) {\n out += string.charAt(i);\n continue;\n }\n\n if (c < 0x80) {\n out = out + hexTable[c];\n continue;\n }\n\n if (c < 0x800) {\n out = out + (hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)]);\n continue;\n }\n\n if (c < 0xD800 || c >= 0xE000) {\n out = out + (hexTable[0xE0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]);\n continue;\n }\n\n i += 1;\n c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));\n out += hexTable[0xF0 | (c >> 18)]\n + hexTable[0x80 | ((c >> 12) & 0x3F)]\n + hexTable[0x80 | ((c >> 6) & 0x3F)]\n + hexTable[0x80 | (c & 0x3F)];\n }\n\n return out;\n};\n\nvar compact = function compact(value) {\n var queue = [{ obj: { o: value }, prop: 'o' }];\n var refs = [];\n\n for (var i = 0; i < queue.length; ++i) {\n var item = queue[i];\n var obj = item.obj[item.prop];\n\n var keys = Object.keys(obj);\n for (var j = 0; j < keys.length; ++j) {\n var key = keys[j];\n var val = obj[key];\n if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {\n queue.push({ obj: obj, prop: key });\n refs.push(val);\n }\n }\n }\n\n compactQueue(queue);\n\n return value;\n};\n\nvar isRegExp = function isRegExp(obj) {\n return Object.prototype.toString.call(obj) === '[object RegExp]';\n};\n\nvar isBuffer = function isBuffer(obj) {\n if (!obj || typeof obj !== 'object') {\n return false;\n }\n\n return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));\n};\n\nvar combine = function combine(a, b) {\n return [].concat(a, b);\n};\n\nmodule.exports = {\n arrayToObject: arrayToObject,\n assign: assign,\n combine: combine,\n compact: compact,\n decode: decode,\n encode: encode,\n isBuffer: isBuffer,\n isRegExp: isRegExp,\n merge: merge\n};\n","'use strict';\n\nvar utils = require('./utils');\nvar formats = require('./formats');\nvar has = Object.prototype.hasOwnProperty;\n\nvar arrayPrefixGenerators = {\n brackets: function brackets(prefix) { // eslint-disable-line func-name-matching\n return prefix + '[]';\n },\n comma: 'comma',\n indices: function indices(prefix, key) { // eslint-disable-line func-name-matching\n return prefix + '[' + key + ']';\n },\n repeat: function repeat(prefix) { // eslint-disable-line func-name-matching\n return prefix;\n }\n};\n\nvar isArray = Array.isArray;\nvar push = Array.prototype.push;\nvar pushToArray = function (arr, valueOrArray) {\n push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);\n};\n\nvar toISO = Date.prototype.toISOString;\n\nvar defaultFormat = formats['default'];\nvar defaults = {\n addQueryPrefix: false,\n allowDots: false,\n charset: 'utf-8',\n charsetSentinel: false,\n delimiter: '&',\n encode: true,\n encoder: utils.encode,\n encodeValuesOnly: false,\n format: defaultFormat,\n formatter: formats.formatters[defaultFormat],\n // deprecated\n indices: false,\n serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching\n return toISO.call(date);\n },\n skipNulls: false,\n strictNullHandling: false\n};\n\nvar isNonNullishPrimitive = function isNonNullishPrimitive(v) { // eslint-disable-line func-name-matching\n return typeof v === 'string'\n || typeof v === 'number'\n || typeof v === 'boolean'\n || typeof v === 'symbol'\n || typeof v === 'bigint'; // eslint-disable-line valid-typeof\n};\n\nvar stringify = function stringify( // eslint-disable-line func-name-matching\n object,\n prefix,\n generateArrayPrefix,\n strictNullHandling,\n skipNulls,\n encoder,\n filter,\n sort,\n allowDots,\n serializeDate,\n formatter,\n encodeValuesOnly,\n charset\n) {\n var obj = object;\n if (typeof filter === 'function') {\n obj = filter(prefix, obj);\n } else if (obj instanceof Date) {\n obj = serializeDate(obj);\n } else if (generateArrayPrefix === 'comma' && isArray(obj)) {\n obj = obj.join(',');\n }\n\n if (obj === null) {\n if (strictNullHandling) {\n return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix;\n }\n\n obj = '';\n }\n\n if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {\n if (encoder) {\n var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset);\n return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))];\n }\n return [formatter(prefix) + '=' + formatter(String(obj))];\n }\n\n var values = [];\n\n if (typeof obj === 'undefined') {\n return values;\n }\n\n var objKeys;\n if (isArray(filter)) {\n objKeys = filter;\n } else {\n var keys = Object.keys(obj);\n objKeys = sort ? keys.sort(sort) : keys;\n }\n\n for (var i = 0; i < objKeys.length; ++i) {\n var key = objKeys[i];\n\n if (skipNulls && obj[key] === null) {\n continue;\n }\n\n if (isArray(obj)) {\n pushToArray(values, stringify(\n obj[key],\n typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix,\n generateArrayPrefix,\n strictNullHandling,\n skipNulls,\n encoder,\n filter,\n sort,\n allowDots,\n serializeDate,\n formatter,\n encodeValuesOnly,\n charset\n ));\n } else {\n pushToArray(values, stringify(\n obj[key],\n prefix + (allowDots ? '.' + key : '[' + key + ']'),\n generateArrayPrefix,\n strictNullHandling,\n skipNulls,\n encoder,\n filter,\n sort,\n allowDots,\n serializeDate,\n formatter,\n encodeValuesOnly,\n charset\n ));\n }\n }\n\n return values;\n};\n\nvar normalizeStringifyOptions = function normalizeStringifyOptions(opts) {\n if (!opts) {\n return defaults;\n }\n\n if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {\n throw new TypeError('Encoder has to be a function.');\n }\n\n var charset = opts.charset || defaults.charset;\n if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {\n throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');\n }\n\n var format = formats['default'];\n if (typeof opts.format !== 'undefined') {\n if (!has.call(formats.formatters, opts.format)) {\n throw new TypeError('Unknown format option provided.');\n }\n format = opts.format;\n }\n var formatter = formats.formatters[format];\n\n var filter = defaults.filter;\n if (typeof opts.filter === 'function' || isArray(opts.filter)) {\n filter = opts.filter;\n }\n\n return {\n addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix,\n allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,\n charset: charset,\n charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,\n delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter,\n encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode,\n encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder,\n encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly,\n filter: filter,\n formatter: formatter,\n serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate,\n skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls,\n sort: typeof opts.sort === 'function' ? opts.sort : null,\n strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling\n };\n};\n\nmodule.exports = function (object, opts) {\n var obj = object;\n var options = normalizeStringifyOptions(opts);\n\n var objKeys;\n var filter;\n\n if (typeof options.filter === 'function') {\n filter = options.filter;\n obj = filter('', obj);\n } else if (isArray(options.filter)) {\n filter = options.filter;\n objKeys = filter;\n }\n\n var keys = [];\n\n if (typeof obj !== 'object' || obj === null) {\n return '';\n }\n\n var arrayFormat;\n if (opts && opts.arrayFormat in arrayPrefixGenerators) {\n arrayFormat = opts.arrayFormat;\n } else if (opts && 'indices' in opts) {\n arrayFormat = opts.indices ? 'indices' : 'repeat';\n } else {\n arrayFormat = 'indices';\n }\n\n var generateArrayPrefix = arrayPrefixGenerators[arrayFormat];\n\n if (!objKeys) {\n objKeys = Object.keys(obj);\n }\n\n if (options.sort) {\n objKeys.sort(options.sort);\n }\n\n for (var i = 0; i < objKeys.length; ++i) {\n var key = objKeys[i];\n\n if (options.skipNulls && obj[key] === null) {\n continue;\n }\n pushToArray(keys, stringify(\n obj[key],\n key,\n generateArrayPrefix,\n options.strictNullHandling,\n options.skipNulls,\n options.encode ? options.encoder : null,\n options.filter,\n options.sort,\n options.allowDots,\n options.serializeDate,\n options.formatter,\n options.encodeValuesOnly,\n options.charset\n ));\n }\n\n var joined = keys.join(options.delimiter);\n var prefix = options.addQueryPrefix === true ? '?' : '';\n\n if (options.charsetSentinel) {\n if (options.charset === 'iso-8859-1') {\n // encodeURIComponent('✓'), the \"numeric entity\" representation of a checkmark\n prefix += 'utf8=%26%2310003%3B&';\n } else {\n // encodeURIComponent('✓')\n prefix += 'utf8=%E2%9C%93&';\n }\n }\n\n return joined.length > 0 ? prefix + joined : '';\n};\n","'use strict';\n\nvar utils = require('./utils');\n\nvar has = Object.prototype.hasOwnProperty;\n\nvar defaults = {\n allowDots: false,\n allowPrototypes: false,\n arrayLimit: 20,\n charset: 'utf-8',\n charsetSentinel: false,\n comma: false,\n decoder: utils.decode,\n delimiter: '&',\n depth: 5,\n ignoreQueryPrefix: false,\n interpretNumericEntities: false,\n parameterLimit: 1000,\n parseArrays: true,\n plainObjects: false,\n strictNullHandling: false\n};\n\nvar interpretNumericEntities = function (str) {\n return str.replace(/&#(\\d+);/g, function ($0, numberStr) {\n return String.fromCharCode(parseInt(numberStr, 10));\n });\n};\n\n// This is what browsers will submit when the ✓ character occurs in an\n// application/x-www-form-urlencoded body and the encoding of the page containing\n// the form is iso-8859-1, or when the submitted form has an accept-charset\n// attribute of iso-8859-1. Presumably also with other charsets that do not contain\n// the ✓ character, such as us-ascii.\nvar isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('✓')\n\n// These are the percent-encoded utf-8 octets representing a checkmark, indicating that the request actually is utf-8 encoded.\nvar charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')\n\nvar parseValues = function parseQueryStringValues(str, options) {\n var obj = {};\n var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\\?/, '') : str;\n var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;\n var parts = cleanStr.split(options.delimiter, limit);\n var skipIndex = -1; // Keep track of where the utf8 sentinel was found\n var i;\n\n var charset = options.charset;\n if (options.charsetSentinel) {\n for (i = 0; i < parts.length; ++i) {\n if (parts[i].indexOf('utf8=') === 0) {\n if (parts[i] === charsetSentinel) {\n charset = 'utf-8';\n } else if (parts[i] === isoSentinel) {\n charset = 'iso-8859-1';\n }\n skipIndex = i;\n i = parts.length; // The eslint settings do not allow break;\n }\n }\n }\n\n for (i = 0; i < parts.length; ++i) {\n if (i === skipIndex) {\n continue;\n }\n var part = parts[i];\n\n var bracketEqualsPos = part.indexOf(']=');\n var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;\n\n var key, val;\n if (pos === -1) {\n key = options.decoder(part, defaults.decoder, charset);\n val = options.strictNullHandling ? null : '';\n } else {\n key = options.decoder(part.slice(0, pos), defaults.decoder, charset);\n val = options.decoder(part.slice(pos + 1), defaults.decoder, charset);\n }\n\n if (val && options.interpretNumericEntities && charset === 'iso-8859-1') {\n val = interpretNumericEntities(val);\n }\n\n if (val && options.comma && val.indexOf(',') > -1) {\n val = val.split(',');\n }\n\n if (has.call(obj, key)) {\n obj[key] = utils.combine(obj[key], val);\n } else {\n obj[key] = val;\n }\n }\n\n return obj;\n};\n\nvar parseObject = function (chain, val, options) {\n var leaf = val;\n\n for (var i = chain.length - 1; i >= 0; --i) {\n var obj;\n var root = chain[i];\n\n if (root === '[]' && options.parseArrays) {\n obj = [].concat(leaf);\n } else {\n obj = options.plainObjects ? Object.create(null) : {};\n var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;\n var index = parseInt(cleanRoot, 10);\n if (!options.parseArrays && cleanRoot === '') {\n obj = { 0: leaf };\n } else if (\n !isNaN(index)\n && root !== cleanRoot\n && String(index) === cleanRoot\n && index >= 0\n && (options.parseArrays && index <= options.arrayLimit)\n ) {\n obj = [];\n obj[index] = leaf;\n } else {\n obj[cleanRoot] = leaf;\n }\n }\n\n leaf = obj;\n }\n\n return leaf;\n};\n\nvar parseKeys = function parseQueryStringKeys(givenKey, val, options) {\n if (!givenKey) {\n return;\n }\n\n // Transform dot notation to bracket notation\n var key = options.allowDots ? givenKey.replace(/\\.([^.[]+)/g, '[$1]') : givenKey;\n\n // The regex chunks\n\n var brackets = /(\\[[^[\\]]*])/;\n var child = /(\\[[^[\\]]*])/g;\n\n // Get the parent\n\n var segment = options.depth > 0 && brackets.exec(key);\n var parent = segment ? key.slice(0, segment.index) : key;\n\n // Stash the parent if it exists\n\n var keys = [];\n if (parent) {\n // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties\n if (!options.plainObjects && has.call(Object.prototype, parent)) {\n if (!options.allowPrototypes) {\n return;\n }\n }\n\n keys.push(parent);\n }\n\n // Loop through children appending to the array until we hit depth\n\n var i = 0;\n while (options.depth > 0 && (segment = child.exec(key)) !== null && i < options.depth) {\n i += 1;\n if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {\n if (!options.allowPrototypes) {\n return;\n }\n }\n keys.push(segment[1]);\n }\n\n // If there's a remainder, just add whatever is left\n\n if (segment) {\n keys.push('[' + key.slice(segment.index) + ']');\n }\n\n return parseObject(keys, val, options);\n};\n\nvar normalizeParseOptions = function normalizeParseOptions(opts) {\n if (!opts) {\n return defaults;\n }\n\n if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') {\n throw new TypeError('Decoder has to be a function.');\n }\n\n if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {\n throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined');\n }\n var charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset;\n\n return {\n allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,\n allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes,\n arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit,\n charset: charset,\n charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,\n comma: typeof opts.comma === 'boolean' ? opts.comma : defaults.comma,\n decoder: typeof opts.decoder === 'function' ? opts.decoder : defaults.decoder,\n delimiter: typeof opts.delimiter === 'string' || utils.isRegExp(opts.delimiter) ? opts.delimiter : defaults.delimiter,\n // eslint-disable-next-line no-implicit-coercion, no-extra-parens\n depth: (typeof opts.depth === 'number' || opts.depth === false) ? +opts.depth : defaults.depth,\n ignoreQueryPrefix: opts.ignoreQueryPrefix === true,\n interpretNumericEntities: typeof opts.interpretNumericEntities === 'boolean' ? opts.interpretNumericEntities : defaults.interpretNumericEntities,\n parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit,\n parseArrays: opts.parseArrays !== false,\n plainObjects: typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects,\n strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling\n };\n};\n\nmodule.exports = function (str, opts) {\n var options = normalizeParseOptions(opts);\n\n if (str === '' || str === null || typeof str === 'undefined') {\n return options.plainObjects ? Object.create(null) : {};\n }\n\n var tempObj = typeof str === 'string' ? parseValues(str, options) : str;\n var obj = options.plainObjects ? Object.create(null) : {};\n\n // Iterate over the keys and setup the new object\n\n var keys = Object.keys(tempObj);\n for (var i = 0; i < keys.length; ++i) {\n var key = keys[i];\n var newObj = parseKeys(key, tempObj[key], options);\n obj = utils.merge(obj, newObj, options);\n }\n\n return utils.compact(obj);\n};\n","import qs from 'qs';\nimport { Router, RouteState } from '../../types';\n\ntype CreateURL = ({\n qsModule,\n routeState,\n location,\n}: {\n qsModule: typeof qs;\n routeState: RouteState;\n location: Location;\n}) => string;\n\ntype ParseURL = ({\n qsModule,\n location,\n}: {\n qsModule: typeof qs;\n location: Location;\n}) => RouteState;\n\ntype BrowserHistoryArgs = {\n windowTitle?: (routeState: RouteState) => string;\n writeDelay?: number;\n createURL?: CreateURL;\n parseURL?: ParseURL;\n};\n\nconst defaultCreateURL: CreateURL = ({ qsModule, routeState, location }) => {\n const { protocol, hostname, port = '', pathname, hash } = location;\n const queryString = qsModule.stringify(routeState);\n const portWithPrefix = port === '' ? '' : `:${port}`;\n\n // IE <= 11 has no proper `location.origin` so we cannot rely on it.\n if (!queryString) {\n return `${protocol}//${hostname}${portWithPrefix}${pathname}${hash}`;\n }\n\n return `${protocol}//${hostname}${portWithPrefix}${pathname}?${queryString}${hash}`;\n};\n\nconst defaultParseURL: ParseURL = ({ qsModule, location }) => {\n // `qs` by default converts arrays with more than 20 items to an object.\n // We want to avoid this because the data structure manipulated can therefore vary.\n // Setting the limit to `100` seems a good number because the engine's default is 100\n // (it can go up to 1000 but it is very unlikely to select more than 100 items in the UI).\n //\n // Using an `arrayLimit` of `n` allows `n + 1` items.\n //\n // See:\n // - https://github.com/ljharb/qs#parsing-arrays\n // - https://www.algolia.com/doc/api-reference/api-parameters/maxValuesPerFacet/\n return qsModule.parse(location.search.slice(1), { arrayLimit: 99 });\n};\n\nconst setWindowTitle = (title?: string): void => {\n if (title) {\n window.document.title = title;\n }\n};\n\nclass BrowserHistory implements Router {\n /**\n * Transforms a UI state into a title for the page.\n */\n private readonly windowTitle?: BrowserHistoryArgs['windowTitle'];\n /**\n * Time in milliseconds before performing a write in the history.\n * It prevents from adding too many entries in the history and\n * makes the back button more usable.\n *\n * @default 400\n */\n private readonly writeDelay: Required['writeDelay'];\n /**\n * Creates a full URL based on the route state.\n * The storage adaptor maps all syncable keys to the query string of the URL.\n */\n private readonly _createURL: Required['createURL'];\n /**\n * Parses the URL into a route state.\n * It should be symetrical to `createURL`.\n */\n private readonly parseURL: Required['parseURL'];\n\n private writeTimer?: number;\n private _onPopState?(event: PopStateEvent): void;\n\n /**\n * Initializes a new storage provider that syncs the search state to the URL\n * using web APIs (`window.location.pushState` and `onpopstate` event).\n */\n public constructor(\n {\n windowTitle,\n writeDelay = 400,\n createURL = defaultCreateURL,\n parseURL = defaultParseURL,\n }: BrowserHistoryArgs = {} as BrowserHistoryArgs\n ) {\n this.windowTitle = windowTitle;\n this.writeTimer = undefined;\n this.writeDelay = writeDelay;\n this._createURL = createURL;\n this.parseURL = parseURL;\n\n const title = this.windowTitle && this.windowTitle(this.read());\n\n setWindowTitle(title);\n }\n\n /**\n * Reads the URL and returns a syncable UI search state.\n */\n public read(): RouteState {\n return this.parseURL({ qsModule: qs, location: window.location });\n }\n\n /**\n * Pushes a search state into the URL.\n */\n public write(routeState: RouteState): void {\n const url = this.createURL(routeState);\n const title = this.windowTitle && this.windowTitle(routeState);\n\n if (this.writeTimer) {\n window.clearTimeout(this.writeTimer);\n }\n\n this.writeTimer = window.setTimeout(() => {\n setWindowTitle(title);\n window.history.pushState(routeState, title || '', url);\n this.writeTimer = undefined;\n }, this.writeDelay);\n }\n\n /**\n * Sets a callback on the `onpopstate` event of the history API of the current page.\n * It enables the URL sync to keep track of the changes.\n */\n public onUpdate(callback: (routeState: RouteState) => void): void {\n this._onPopState = event => {\n if (this.writeTimer) {\n window.clearTimeout(this.writeTimer);\n this.writeTimer = undefined;\n }\n\n const routeState = event.state;\n\n // At initial load, the state is read from the URL without update.\n // Therefore the state object is not available.\n // In this case, we fallback and read the URL.\n if (!routeState) {\n callback(this.read());\n } else {\n callback(routeState);\n }\n };\n\n window.addEventListener('popstate', this._onPopState);\n }\n\n /**\n * Creates a complete URL from a given syncable UI state.\n *\n * It always generates the full URL, not a relative one.\n * This allows to handle cases like using a .\n * See: https://github.com/algolia/instantsearch.js/issues/790\n */\n public createURL(routeState: RouteState): string {\n return this._createURL({\n qsModule: qs,\n routeState,\n location: window.location,\n });\n }\n\n /**\n * Removes the event listener and cleans up the URL.\n */\n public dispose(): void {\n if (this._onPopState) {\n window.removeEventListener('popstate', this._onPopState);\n }\n\n if (this.writeTimer) {\n window.clearTimeout(this.writeTimer);\n }\n\n this.write({});\n }\n}\n\nexport default function(props?: BrowserHistoryArgs): BrowserHistory {\n return new BrowserHistory(props);\n}\n","'use strict';\n\nvar replace = String.prototype.replace;\nvar percentTwenties = /%20/g;\n\nvar util = require('./utils');\n\nvar Format = {\n RFC1738: 'RFC1738',\n RFC3986: 'RFC3986'\n};\n\nmodule.exports = util.assign(\n {\n 'default': Format.RFC3986,\n formatters: {\n RFC1738: function (value) {\n return replace.call(value, percentTwenties, '+');\n },\n RFC3986: function (value) {\n return String(value);\n }\n }\n },\n Format\n);\n","'use strict';\n\nvar stringify = require('./stringify');\nvar parse = require('./parse');\nvar formats = require('./formats');\n\nmodule.exports = {\n formats: formats,\n parse: parse,\n stringify: stringify\n};\n","import simpleStateMapping from '../lib/stateMappings/simple';\nimport historyRouter from '../lib/routers/history';\nimport {\n Router,\n StateMapping,\n UiState,\n Middleware,\n RouteState,\n} from '../types';\nimport { isEqual } from '../lib/utils';\n\nexport type RouterProps = {\n router?: Router;\n stateMapping?: StateMapping;\n};\n\nexport type RoutingManager = (props?: RouterProps) => Middleware;\n\nexport const createRouterMiddleware: RoutingManager = (props = {}) => {\n const {\n router = historyRouter(),\n stateMapping = simpleStateMapping(),\n } = props;\n\n return ({ instantSearchInstance }) => {\n function topLevelCreateURL(nextState: UiState) {\n const uiState: UiState = Object.keys(nextState).reduce(\n (acc, indexId) => ({\n ...acc,\n [indexId]: nextState[indexId],\n }),\n instantSearchInstance.mainIndex.getWidgetUiState({})\n );\n\n const route = stateMapping.stateToRoute(uiState);\n\n return router.createURL(route);\n }\n\n instantSearchInstance._createURL = topLevelCreateURL;\n instantSearchInstance._initialUiState = {\n ...instantSearchInstance._initialUiState,\n ...stateMapping.routeToState(router.read()),\n };\n\n let lastRouteState: RouteState | undefined = undefined;\n\n return {\n onStateChange({ uiState }) {\n const routeState = stateMapping.stateToRoute(uiState);\n\n if (\n lastRouteState === undefined ||\n !isEqual(lastRouteState, routeState)\n ) {\n router.write(routeState);\n lastRouteState = routeState;\n }\n },\n\n subscribe() {\n router.onUpdate(route => {\n instantSearchInstance.setUiState(stateMapping.routeToState(route));\n });\n },\n\n unsubscribe() {\n router.dispose();\n },\n };\n };\n};\n","import { InstantSearch, Middleware, Widget } from '../types';\nimport { Index } from '../widgets/index/index';\n\ntype WidgetMetaData = {\n type: string | undefined;\n widgetType: string | undefined;\n params: string[];\n};\n\ntype Payload = {\n widgets: WidgetMetaData[];\n};\n\nfunction extractPayload(\n widgets: Array>,\n instantSearchInstance: InstantSearch,\n payload: Payload\n) {\n const parent = instantSearchInstance.mainIndex;\n\n const initOptions = {\n instantSearchInstance,\n parent,\n scopedResults: [],\n state: parent.getHelper()!.state,\n helper: parent.getHelper()!,\n createURL: parent.createURL,\n uiState: instantSearchInstance._initialUiState,\n renderState: instantSearchInstance.renderState,\n templatesConfig: instantSearchInstance.templatesConfig,\n searchMetadata: {\n isSearchStalled: instantSearchInstance._isSearchStalled,\n },\n };\n\n widgets.forEach(widget => {\n let widgetParams = {};\n\n if (widget.getWidgetRenderState) {\n const renderState = widget.getWidgetRenderState(initOptions);\n\n if (renderState && renderState.widgetParams) {\n widgetParams = renderState.widgetParams;\n }\n }\n\n // since we destructure in all widgets, the parameters with defaults are set to \"undefined\"\n const params = Object.keys(widgetParams).filter(\n key => widgetParams[key] !== undefined\n );\n\n payload.widgets.push({\n type: widget.$$type,\n widgetType: widget.$$widgetType,\n params,\n });\n\n if (widget.$$type === 'ais.index') {\n extractPayload(\n (widget as Index).getWidgets(),\n instantSearchInstance,\n payload\n );\n }\n });\n}\n\nexport function isMetadataEnabled() {\n return (\n typeof window !== 'undefined' &&\n window.navigator.userAgent.indexOf('Algolia Crawler') > -1\n );\n}\n\n/**\n * Exposes the metadata of mounted widgets in a custom\n * `` tag. The metadata per widget is:\n * - applied parameters\n * - widget name\n * - connector name\n */\nexport function createMetadataMiddleware(): Middleware {\n return ({ instantSearchInstance }) => {\n const payload: Payload = {\n widgets: [],\n };\n const payloadContainer = document.createElement('meta');\n const refNode = document.querySelector('head')!;\n payloadContainer.name = 'instantsearch:widgets';\n\n return {\n onStateChange() {},\n subscribe() {\n // using setTimeout here to delay extraction until widgets have been added in a tick (e.g. Vue)\n setTimeout(() => {\n extractPayload(\n instantSearchInstance.mainIndex.getWidgets(),\n instantSearchInstance,\n payload\n );\n\n payloadContainer.content = JSON.stringify(payload);\n refNode.appendChild(payloadContainer);\n }, 0);\n },\n\n unsubscribe() {\n payloadContainer.parentNode?.removeChild(payloadContainer);\n },\n };\n };\n}\n","import algoliasearchHelper, { AlgoliaSearchHelper } from 'algoliasearch-helper';\nimport EventEmitter from 'events';\nimport index, { Index, isIndexWidget } from '../widgets/index/index';\nimport version from './version';\nimport createHelpers from './createHelpers';\nimport {\n createDocumentationMessageGenerator,\n createDocumentationLink,\n defer,\n noop,\n warning,\n checkIndexUiState,\n} from './utils';\nimport {\n InsightsClient as AlgoliaInsightsClient,\n SearchClient,\n Widget,\n UiState,\n CreateURL,\n Middleware,\n MiddlewareDefinition,\n RenderState,\n} from '../types';\nimport {\n createRouterMiddleware,\n RouterProps,\n} from '../middlewares/createRouterMiddleware';\nimport { InsightsEvent } from '../middlewares/createInsightsMiddleware';\nimport {\n createMetadataMiddleware,\n isMetadataEnabled,\n} from '../middlewares/createMetadataMiddleware';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'instantsearch',\n});\n\nfunction defaultCreateURL() {\n return '#';\n}\n\n/**\n * Global options for an InstantSearch instance.\n */\nexport type InstantSearchOptions = {\n /**\n * The name of the main index\n */\n indexName: string;\n\n /**\n * The search client to plug to InstantSearch.js\n *\n * Usage:\n * ```javascript\n * // Using the default Algolia search client\n * instantsearch({\n * indexName: 'indexName',\n * searchClient: algoliasearch('appId', 'apiKey')\n * });\n *\n * // Using a custom search client\n * instantsearch({\n * indexName: 'indexName',\n * searchClient: {\n * search(requests) {\n * // fetch response based on requests\n * return response;\n * },\n * searchForFacetValues(requests) {\n * // fetch response based on requests\n * return response;\n * }\n * }\n * });\n * ```\n */\n searchClient: SearchClient;\n\n /**\n * The locale used to display numbers. This will be passed\n * to `Number.prototype.toLocaleString()`\n */\n numberLocale?: string;\n\n /**\n * A hook that will be called each time a search needs to be done, with the\n * helper as a parameter. It's your responsibility to call `helper.search()`.\n * This option allows you to avoid doing searches at page load for example.\n */\n searchFunction?: (helper: AlgoliaSearchHelper) => void;\n\n /**\n * Function called when the state changes.\n *\n * Using this function makes the instance controlled. This means that you\n * become in charge of updating the UI state with the `setUiState` function.\n */\n onStateChange?: (params: {\n uiState: UiState;\n setUiState(uiState: UiState | ((uiState: UiState) => UiState)): void;\n }) => void;\n\n /**\n * Injects a `uiState` to the `instantsearch` instance. You can use this option\n * to provide an initial state to a widget. Note that the state is only used\n * for the first search. To unconditionally pass additional parameters to the\n * Algolia API, take a look at the `configure` widget.\n */\n initialUiState?: UiState;\n\n /**\n * Time before a search is considered stalled. The default is 200ms\n */\n stalledSearchDelay?: number;\n\n /**\n * Router configuration used to save the UI State into the URL or any other\n * client side persistence. Passing `true` will use the default URL options.\n */\n routing?: RouterProps | boolean;\n\n /**\n * the instance of search-insights to use for sending insights events inside\n * widgets like `hits`.\n *\n * @deprecated This property will be still supported in 4.x releases, but not further. It is replaced by the `insights` middleware. For more information, visit https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-through-and-conversions/how-to/send-click-and-conversion-events-with-instantsearch/js/\n */\n insightsClient?: AlgoliaInsightsClient;\n};\n\n/**\n * The actual implementation of the InstantSearch. This is\n * created using the `instantsearch` factory function.\n * It emits the 'render' event every time a search is done\n */\nclass InstantSearch extends EventEmitter {\n public client: InstantSearchOptions['searchClient'];\n public indexName: string;\n public insightsClient: AlgoliaInsightsClient | null;\n public onStateChange: InstantSearchOptions['onStateChange'] | null = null;\n public helper: AlgoliaSearchHelper | null;\n public mainHelper: AlgoliaSearchHelper | null;\n public mainIndex: Index;\n public started: boolean;\n public templatesConfig: object;\n public renderState: RenderState = {};\n public _stalledSearchDelay: number;\n public _searchStalledTimer: any;\n public _isSearchStalled: boolean;\n public _initialUiState: UiState;\n public _createURL: CreateURL;\n public _searchFunction?: InstantSearchOptions['searchFunction'];\n public _mainHelperSearch?: AlgoliaSearchHelper['search'];\n public middleware: MiddlewareDefinition[] = [];\n public sendEventToInsights: (event: InsightsEvent) => void;\n\n public constructor(options: InstantSearchOptions) {\n super();\n\n const {\n indexName = null,\n numberLocale,\n initialUiState = {},\n routing = null,\n searchFunction,\n stalledSearchDelay = 200,\n searchClient = null,\n insightsClient = null,\n onStateChange = null,\n } = options;\n\n if (indexName === null) {\n throw new Error(withUsage('The `indexName` option is required.'));\n }\n\n if (searchClient === null) {\n throw new Error(withUsage('The `searchClient` option is required.'));\n }\n\n if (typeof (searchClient as any).search !== 'function') {\n throw new Error(\n `The \\`searchClient\\` must implement a \\`search\\` method.\n\nSee: https://www.algolia.com/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-instantsearch/js/`\n );\n }\n\n if (typeof searchClient.addAlgoliaAgent === 'function') {\n searchClient.addAlgoliaAgent(`instantsearch.js (${version})`);\n }\n\n warning(\n insightsClient === null,\n `\\`insightsClient\\` property has been deprecated. It is still supported in 4.x releases, but not further. It is replaced by the \\`insights\\` middleware.\n\nFor more information, visit https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-through-and-conversions/how-to/send-click-and-conversion-events-with-instantsearch/js/`\n );\n\n if (insightsClient && typeof insightsClient !== 'function') {\n throw new Error(\n withUsage('The `insightsClient` option should be a function.')\n );\n }\n\n warning(\n !(options as any).searchParameters,\n `The \\`searchParameters\\` option is deprecated and will not be supported in InstantSearch.js 4.x.\n\nYou can replace it with the \\`configure\\` widget:\n\n\\`\\`\\`\nsearch.addWidgets([\n configure(${JSON.stringify((options as any).searchParameters, null, 2)})\n]);\n\\`\\`\\`\n\nSee ${createDocumentationLink({\n name: 'configure',\n })}`\n );\n\n this.client = searchClient;\n this.insightsClient = insightsClient;\n this.indexName = indexName;\n this.helper = null;\n this.mainHelper = null;\n this.mainIndex = index({\n indexName,\n });\n this.onStateChange = onStateChange;\n\n this.started = false;\n this.templatesConfig = {\n helpers: createHelpers({ numberLocale }),\n compileOptions: {},\n };\n\n this._stalledSearchDelay = stalledSearchDelay;\n this._searchStalledTimer = null;\n this._isSearchStalled = false;\n\n this._createURL = defaultCreateURL;\n this._initialUiState = initialUiState;\n\n if (searchFunction) {\n this._searchFunction = searchFunction;\n }\n\n this.sendEventToInsights = noop;\n\n if (routing) {\n const routerOptions = typeof routing === 'boolean' ? undefined : routing;\n this.use(createRouterMiddleware(routerOptions));\n }\n\n if (isMetadataEnabled()) {\n this.use(createMetadataMiddleware());\n }\n }\n\n /**\n * Hooks a middleware into the InstantSearch lifecycle.\n *\n * This method is considered as experimental and is subject to change in\n * minor versions.\n */\n public use(...middleware: Middleware[]): this {\n const newMiddlewareList = middleware.map(fn => {\n const newMiddleware = fn({ instantSearchInstance: this });\n this.middleware.push(newMiddleware);\n return newMiddleware;\n });\n\n // If the instance has already started, we directly subscribe the\n // middleware so they're notified of changes.\n if (this.started) {\n newMiddlewareList.forEach(m => {\n m.subscribe();\n });\n }\n\n return this;\n }\n\n // @major we shipped with EXPERIMENTAL_use, but have changed that to just `use` now\n public EXPERIMENTAL_use(...middleware: Middleware[]): this {\n warning(\n false,\n 'The middleware API is now considered stable, so we recommend replacing `EXPERIMENTAL_use` with `use` before upgrading to the next major version.'\n );\n\n return this.use(...middleware);\n }\n\n /**\n * Adds a widget to the search instance.\n * A widget can be added either before or after InstantSearch has started.\n * @param widget The widget to add to InstantSearch.\n *\n * @deprecated This method will still be supported in 4.x releases, but not further. It is replaced by `addWidgets([widget])`.\n */\n public addWidget(widget: Widget) {\n warning(\n false,\n 'addWidget will still be supported in 4.x releases, but not further. It is replaced by `addWidgets([widget])`'\n );\n\n return this.addWidgets([widget]);\n }\n\n /**\n * Adds multiple widgets to the search instance.\n * Widgets can be added either before or after InstantSearch has started.\n * @param widgets The array of widgets to add to InstantSearch.\n */\n public addWidgets(widgets: Widget[]) {\n if (!Array.isArray(widgets)) {\n throw new Error(\n withUsage(\n 'The `addWidgets` method expects an array of widgets. Please use `addWidget`.'\n )\n );\n }\n\n if (\n widgets.some(\n widget =>\n typeof widget.init !== 'function' &&\n typeof widget.render !== 'function'\n )\n ) {\n throw new Error(\n withUsage(\n 'The widget definition expects a `render` and/or an `init` method.'\n )\n );\n }\n\n this.mainIndex.addWidgets(widgets);\n\n return this;\n }\n\n /**\n * Removes a widget from the search instance.\n * @deprecated This method will still be supported in 4.x releases, but not further. It is replaced by `removeWidgets([widget])`\n * @param widget The widget instance to remove from InstantSearch.\n *\n * The widget must implement a `dispose()` method to clear its state.\n */\n public removeWidget(widget: Widget) {\n warning(\n false,\n 'removeWidget will still be supported in 4.x releases, but not further. It is replaced by `removeWidgets([widget])`'\n );\n\n return this.removeWidgets([widget]);\n }\n\n /**\n * Removes multiple widgets from the search instance.\n * @param widgets Array of widgets instances to remove from InstantSearch.\n *\n * The widgets must implement a `dispose()` method to clear their states.\n */\n public removeWidgets(widgets: Widget[]) {\n if (!Array.isArray(widgets)) {\n throw new Error(\n withUsage(\n 'The `removeWidgets` method expects an array of widgets. Please use `removeWidget`.'\n )\n );\n }\n\n if (widgets.some(widget => typeof widget.dispose !== 'function')) {\n throw new Error(\n withUsage('The widget definition expects a `dispose` method.')\n );\n }\n\n this.mainIndex.removeWidgets(widgets);\n\n return this;\n }\n\n /**\n * Ends the initialization of InstantSearch.js and triggers the\n * first search. This method should be called after all widgets have been added\n * to the instance of InstantSearch.js. InstantSearch.js also supports adding and removing\n * widgets after the start as an **EXPERIMENTAL** feature.\n */\n public start() {\n if (this.started) {\n throw new Error(\n withUsage('The `start` method has already been called once.')\n );\n }\n\n // This Helper is used for the queries, we don't care about its state. The\n // states are managed at the `index` level. We use this Helper to create\n // DerivedHelper scoped into the `index` widgets.\n const mainHelper = algoliasearchHelper(this.client, this.indexName);\n\n mainHelper.search = () => {\n // This solution allows us to keep the exact same API for the users but\n // under the hood, we have a different implementation. It should be\n // completely transparent for the rest of the codebase. Only this module\n // is impacted.\n return mainHelper.searchOnlyWithDerivedHelpers();\n };\n\n if (this._searchFunction) {\n // this client isn't used to actually search, but required for the helper\n // to not throw errors\n const fakeClient = ({\n search: () => new Promise(noop),\n } as any) as SearchClient;\n\n this._mainHelperSearch = mainHelper.search.bind(mainHelper);\n mainHelper.search = () => {\n const mainIndexHelper = this.mainIndex.getHelper();\n const searchFunctionHelper = algoliasearchHelper(\n fakeClient,\n mainIndexHelper!.state.index,\n mainIndexHelper!.state\n );\n searchFunctionHelper.once('search', ({ state }) => {\n mainIndexHelper!.overrideStateWithoutTriggeringChangeEvent(state);\n this._mainHelperSearch!();\n });\n // Forward state changes from `searchFunctionHelper` to `mainIndexHelper`\n searchFunctionHelper.on('change', ({ state }) => {\n mainIndexHelper!.setState(state);\n });\n this._searchFunction!(searchFunctionHelper);\n return mainHelper;\n };\n }\n\n // Only the \"main\" Helper emits the `error` event vs the one for `search`\n // and `results` that are also emitted on the derived one.\n mainHelper.on('error', ({ error }) => {\n this.emit('error', {\n error,\n });\n });\n\n this.mainHelper = mainHelper;\n\n this.mainIndex.init({\n instantSearchInstance: this,\n parent: null,\n uiState: this._initialUiState,\n });\n\n this.middleware.forEach(m => {\n m.subscribe();\n });\n\n mainHelper.search();\n\n // Keep the previous reference for legacy purpose, some pattern use\n // the direct Helper access `search.helper` (e.g multi-index).\n this.helper = this.mainIndex.getHelper();\n\n // track we started the search if we add more widgets,\n // to init them directly after add\n this.started = true;\n }\n\n /**\n * Removes all widgets without triggering a search afterwards. This is an **EXPERIMENTAL** feature,\n * if you find an issue with it, please\n * [open an issue](https://github.com/algolia/instantsearch.js/issues/new?title=Problem%20with%20dispose).\n * @return {undefined} This method does not return anything\n */\n public dispose(): void {\n this.scheduleSearch.cancel();\n this.scheduleRender.cancel();\n clearTimeout(this._searchStalledTimer);\n\n this.removeWidgets(this.mainIndex.getWidgets());\n this.mainIndex.dispose();\n\n // You can not start an instance two times, therefore a disposed instance\n // needs to set started as false otherwise this can not be restarted at a\n // later point.\n this.started = false;\n\n // The helper needs to be reset to perform the next search from a fresh state.\n // If not reset, it would use the state stored before calling `dispose()`.\n this.removeAllListeners();\n this.mainHelper!.removeAllListeners();\n this.mainHelper = null;\n this.helper = null;\n\n this.middleware.forEach(m => {\n m.unsubscribe();\n });\n }\n\n public scheduleSearch = defer(() => {\n if (this.started) {\n this.mainHelper!.search();\n }\n });\n\n public scheduleRender = defer(() => {\n if (!this.mainHelper!.hasPendingRequests()) {\n clearTimeout(this._searchStalledTimer);\n this._searchStalledTimer = null;\n this._isSearchStalled = false;\n }\n\n this.mainIndex.render({\n instantSearchInstance: this,\n });\n\n this.emit('render');\n });\n\n public scheduleStalledRender() {\n if (!this._searchStalledTimer) {\n this._searchStalledTimer = setTimeout(() => {\n this._isSearchStalled = true;\n this.scheduleRender();\n }, this._stalledSearchDelay);\n }\n }\n\n public setUiState(uiState: UiState | ((uiState: UiState) => UiState)): void {\n if (!this.mainHelper) {\n throw new Error(\n withUsage('The `start` method needs to be called before `setUiState`.')\n );\n }\n\n // We refresh the index UI state to update the local UI state that the\n // main index passes to the function form of `setUiState`.\n this.mainIndex.refreshUiState();\n const nextUiState =\n typeof uiState === 'function'\n ? uiState(this.mainIndex.getWidgetUiState({}))\n : uiState;\n\n const setIndexHelperState = (indexWidget: Index) => {\n if (__DEV__) {\n checkIndexUiState({\n index: indexWidget,\n indexUiState: nextUiState[indexWidget.getIndexId()],\n });\n }\n\n indexWidget.getHelper()!.overrideStateWithoutTriggeringChangeEvent(\n indexWidget.getWidgetSearchParameters(indexWidget.getHelper()!.state, {\n uiState: nextUiState[indexWidget.getIndexId()],\n })\n );\n\n indexWidget\n .getWidgets()\n .filter(isIndexWidget)\n .forEach(setIndexHelperState);\n };\n\n setIndexHelperState(this.mainIndex);\n\n this.scheduleSearch();\n this.onInternalStateChange();\n }\n\n public onInternalStateChange = () => {\n const nextUiState = this.mainIndex.getWidgetUiState({});\n\n this.middleware.forEach(m => {\n m.onStateChange({\n uiState: nextUiState,\n });\n });\n };\n\n public createURL(nextState: UiState = {}): string {\n if (!this.started) {\n throw new Error(\n withUsage('The `start` method needs to be called before `createURL`.')\n );\n }\n\n return this._createURL(nextState);\n }\n\n public refresh() {\n if (!this.mainHelper) {\n throw new Error(\n withUsage('The `start` method needs to be called before `refresh`.')\n );\n }\n\n this.mainHelper.clearCache().search();\n }\n}\n\nexport default InstantSearch;\n","import { AlgoliaSearchHelper } from 'algoliasearch-helper';\nimport {\n checkRendering,\n clearRefinements,\n getRefinements,\n createDocumentationMessageGenerator,\n noop,\n uniq,\n mergeSearchParameters,\n} from '../../lib/utils';\nimport { TransformItems, CreateURL, Connector } from '../../types';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'clear-refinements',\n connector: true,\n});\n\nexport type ClearRefinementsConnectorParams = {\n /**\n * The attributes to include in the refinements to clear (all by default). Cannot be used with `excludedAttributes`.\n */\n includedAttributes?: string[];\n\n /**\n * The attributes to exclude from the refinements to clear. Cannot be used with `includedAttributes`.\n */\n excludedAttributes?: string[];\n\n /**\n * Function to transform the items passed to the templates.\n */\n transformItems?: TransformItems;\n};\n\nexport type ClearRefinementsRendererOptions = {\n /**\n * Triggers the clear of all the currently refined values.\n */\n refine: () => void;\n\n /**\n * Indicates if search state is refined.\n */\n hasRefinements: boolean;\n\n /**\n * Creates a url for the next state when refinements are cleared.\n */\n createURL: CreateURL;\n};\n\nexport type ClearRefinementsConnector = Connector<\n ClearRefinementsRendererOptions,\n ClearRefinementsConnectorParams\n>;\n\ntype AttributesToClear = {\n helper: AlgoliaSearchHelper;\n items: string[];\n};\n\nconst connectClearRefinements: ClearRefinementsConnector = function connectClearRefinements(\n renderFn,\n unmountFn = noop\n) {\n checkRendering(renderFn, withUsage());\n\n return widgetParams => {\n const {\n includedAttributes = [],\n excludedAttributes = ['query'],\n transformItems = items => items,\n } = widgetParams || ({} as typeof widgetParams);\n\n if (widgetParams.includedAttributes && widgetParams.excludedAttributes) {\n throw new Error(\n withUsage(\n 'The options `includedAttributes` and `excludedAttributes` cannot be used together.'\n )\n );\n }\n\n type ConnectorState = {\n refine(): void;\n createURL(): string;\n attributesToClear: AttributesToClear[];\n };\n\n const connectorState: ConnectorState = {\n refine: noop,\n createURL: () => '',\n attributesToClear: [],\n };\n\n const cachedRefine = () => connectorState.refine();\n const cachedCreateURL = () => connectorState.createURL();\n\n return {\n $$type: 'ais.clearRefinements',\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n dispose() {\n unmountFn();\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n clearRefinements: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState({ createURL, scopedResults }) {\n connectorState.attributesToClear = scopedResults.reduce<\n Array>\n >((results, scopedResult) => {\n return results.concat(\n getAttributesToClear({\n scopedResult,\n includedAttributes,\n excludedAttributes,\n transformItems,\n })\n );\n }, []);\n\n connectorState.refine = () => {\n connectorState.attributesToClear.forEach(\n ({ helper: indexHelper, items }) => {\n indexHelper\n .setState(\n clearRefinements({\n helper: indexHelper,\n attributesToClear: items,\n })\n )\n .search();\n }\n );\n };\n\n connectorState.createURL = () =>\n createURL(\n mergeSearchParameters(\n ...connectorState.attributesToClear.map(\n ({ helper: indexHelper, items }) => {\n return clearRefinements({\n helper: indexHelper,\n attributesToClear: items,\n });\n }\n )\n )\n );\n\n return {\n hasRefinements: connectorState.attributesToClear.some(\n attributeToClear => attributeToClear.items.length > 0\n ),\n refine: cachedRefine,\n createURL: cachedCreateURL,\n widgetParams,\n };\n },\n };\n };\n};\n\nfunction getAttributesToClear({\n scopedResult,\n includedAttributes,\n excludedAttributes,\n transformItems,\n}): AttributesToClear {\n const clearsQuery =\n includedAttributes.indexOf('query') !== -1 ||\n excludedAttributes.indexOf('query') === -1;\n\n return {\n helper: scopedResult.helper,\n items: transformItems(\n uniq(\n getRefinements(\n scopedResult.results,\n scopedResult.helper.state,\n clearsQuery\n )\n .map(refinement => refinement.attribute)\n .filter(\n attribute =>\n // If the array is empty (default case), we keep all the attributes\n includedAttributes.length === 0 ||\n // Otherwise, only add the specified attributes\n includedAttributes.indexOf(attribute) !== -1\n )\n .filter(\n attribute =>\n // If the query is included, we ignore the default `excludedAttributes = ['query']`\n (attribute === 'query' && clearsQuery) ||\n // Otherwise, ignore the excluded attributes\n excludedAttributes.indexOf(attribute) === -1\n )\n )\n ),\n };\n}\n\nexport default connectClearRefinements;\n","export default '4.13.2';\n","import {\n highlight,\n reverseHighlight,\n snippet,\n reverseSnippet,\n HighlightOptions,\n ReverseHighlightOptions,\n SnippetOptions,\n ReverseSnippetOptions,\n insights,\n} from '../helpers';\nimport { Hit, InsightsClientMethod, InsightsClientPayload } from '../types';\n\ntype HoganRenderer = (value: any) => string;\n\ntype HoganHelpers = {\n formatNumber: (value: number, render: HoganRenderer) => string;\n highlight: (options: string, render: HoganRenderer) => string;\n reverseHighlight: (options: string, render: HoganRenderer) => string;\n snippet: (options: string, render: HoganRenderer) => string;\n reverseSnippet: (options: string, render: HoganRenderer) => string;\n insights: (options: string, render: HoganRenderer) => string;\n};\n\nexport default function hoganHelpers({\n numberLocale,\n}: {\n numberLocale?: string;\n}): HoganHelpers {\n return {\n formatNumber(value, render) {\n return Number(render(value)).toLocaleString(numberLocale);\n },\n highlight(options, render) {\n try {\n const highlightOptions: Omit = JSON.parse(\n options\n );\n\n return render(\n highlight({\n ...highlightOptions,\n hit: this,\n })\n );\n } catch (error) {\n throw new Error(`\nThe highlight helper expects a JSON object of the format:\n{ \"attribute\": \"name\", \"highlightedTagName\": \"mark\" }`);\n }\n },\n reverseHighlight(options, render) {\n try {\n const reverseHighlightOptions: Omit<\n ReverseHighlightOptions,\n 'hit'\n > = JSON.parse(options);\n\n return render(\n reverseHighlight({\n ...reverseHighlightOptions,\n hit: this,\n })\n );\n } catch (error) {\n throw new Error(`\n The reverseHighlight helper expects a JSON object of the format:\n { \"attribute\": \"name\", \"highlightedTagName\": \"mark\" }`);\n }\n },\n snippet(options, render) {\n try {\n const snippetOptions: Omit = JSON.parse(options);\n\n return render(\n snippet({\n ...snippetOptions,\n hit: this,\n })\n );\n } catch (error) {\n throw new Error(`\nThe snippet helper expects a JSON object of the format:\n{ \"attribute\": \"name\", \"highlightedTagName\": \"mark\" }`);\n }\n },\n reverseSnippet(options, render) {\n try {\n const reverseSnippetOptions: Omit<\n ReverseSnippetOptions,\n 'hit'\n > = JSON.parse(options);\n\n return render(\n reverseSnippet({\n ...reverseSnippetOptions,\n hit: this,\n })\n );\n } catch (error) {\n throw new Error(`\n The reverseSnippet helper expects a JSON object of the format:\n { \"attribute\": \"name\", \"highlightedTagName\": \"mark\" }`);\n }\n },\n insights(this: Hit, options, render) {\n try {\n type InsightsHelperOptions = {\n method: InsightsClientMethod;\n payload: Partial;\n };\n const { method, payload }: InsightsHelperOptions = JSON.parse(options);\n\n return render(\n insights(method, { objectIDs: [this.objectID], ...payload })\n );\n } catch (error) {\n throw new Error(`\nThe insights helper expects a JSON object of the format:\n{ \"method\": \"method-name\", \"payload\": { \"eventName\": \"name of the event\" } }`);\n }\n },\n };\n}\n","import {\n AlgoliaSearchHelper,\n SearchParameters,\n SearchResults,\n} from 'algoliasearch-helper';\nimport {\n getRefinements,\n checkRendering,\n createDocumentationMessageGenerator,\n noop,\n warning,\n} from '../../lib/utils';\nimport {\n Refinement,\n FacetRefinement,\n NumericRefinement,\n} from '../../lib/utils/getRefinements';\nimport { Connector, TransformItems, CreateURL } from '../../types';\n\nexport type CurrentRefinementsConnectorParamsRefinement = {\n /**\n * The attribute on which the refinement is applied.\n */\n attribute: string;\n\n /**\n * The type of the refinement.\n *\n * It can be one of those: 'facet'|'exclude'|'disjunctive'|'hierarchical'|'numeric'|'query'|'tag'.\n */\n type: string;\n\n /**\n * The raw value of the refinement.\n */\n value: string | number;\n\n /**\n * The label of the refinement to display.\n */\n label: string;\n\n /**\n * The value of the operator (only if applicable).\n */\n operator?: string;\n\n /**\n * The number of found items (only if applicable).\n */\n count?: number;\n\n /**\n * Whether the count is exhaustive (only if applicable).\n */\n exhaustive?: boolean;\n};\n\nexport type CurrentRefinementsConnectorParamsItem = {\n /**\n * The index name.\n */\n indexName: string;\n\n /**\n * The attribute on which the refinement is applied.\n */\n attribute: string;\n\n /**\n * The textual representation of this attribute.\n */\n label: string;\n\n /**\n * Currently applied refinements.\n */\n refinements: CurrentRefinementsConnectorParamsRefinement[];\n\n /**\n * Removes the given refinement and triggers a new search.\n */\n refine(refinement: CurrentRefinementsConnectorParamsRefinement): void;\n};\n\nexport type CurrentRefinementsConnectorParams = {\n /**\n * The attributes to include in the widget (all by default).\n * Cannot be used with `excludedAttributes`.\n *\n * @default `[]`\n */\n includedAttributes?: string[];\n\n /**\n * The attributes to exclude from the widget.\n * Cannot be used with `includedAttributes`.\n *\n * @default `['query']`\n */\n excludedAttributes?: string[];\n\n /**\n * Function to transform the items passed to the templates.\n */\n transformItems?: TransformItems;\n};\n\nexport type CurrentRefinementsRendererOptions = {\n /**\n * All the currently refined items, grouped by attribute.\n */\n items: CurrentRefinementsConnectorParamsItem[];\n\n /**\n * Removes the given refinement and triggers a new search.\n */\n refine(refinement: CurrentRefinementsConnectorParamsRefinement): void;\n\n /**\n * Generates a URL for the next state.\n */\n createURL: CreateURL;\n};\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'current-refinements',\n connector: true,\n});\n\nexport type CurrentRefinementsConnector = Connector<\n CurrentRefinementsRendererOptions,\n CurrentRefinementsConnectorParams\n>;\n\nconst connectCurrentRefinements: CurrentRefinementsConnector = function connectCurrentRefinements(\n renderFn,\n unmountFn = noop\n) {\n checkRendering(renderFn, withUsage());\n\n return widgetParams => {\n if (\n (widgetParams || ({} as typeof widgetParams)).includedAttributes &&\n (widgetParams || ({} as typeof widgetParams)).excludedAttributes\n ) {\n throw new Error(\n withUsage(\n 'The options `includedAttributes` and `excludedAttributes` cannot be used together.'\n )\n );\n }\n\n const {\n includedAttributes,\n excludedAttributes = ['query'],\n transformItems = (items: CurrentRefinementsConnectorParamsItem[]) =>\n items,\n } = widgetParams || ({} as typeof widgetParams);\n\n return {\n $$type: 'ais.currentRefinements',\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n dispose() {\n unmountFn();\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n currentRefinements: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState({ results, scopedResults, createURL, helper }) {\n function getItems() {\n if (!results) {\n return transformItems(\n getRefinementsItems({\n results: {} as SearchResults,\n helper,\n includedAttributes,\n excludedAttributes,\n })\n );\n }\n\n return scopedResults.reduce(\n (accResults, scopedResult) => {\n return accResults.concat(\n transformItems(\n getRefinementsItems({\n results: scopedResult.results,\n helper: scopedResult.helper,\n includedAttributes,\n excludedAttributes,\n })\n )\n );\n },\n []\n );\n }\n\n return {\n items: getItems(),\n refine: refinement => clearRefinement(helper, refinement),\n createURL: refinement =>\n createURL(clearRefinementFromState(helper.state, refinement)),\n widgetParams,\n };\n },\n };\n };\n};\n\nfunction getRefinementsItems({\n results,\n helper,\n includedAttributes,\n excludedAttributes,\n}: {\n results: SearchResults;\n helper: AlgoliaSearchHelper;\n includedAttributes: CurrentRefinementsConnectorParams['includedAttributes'];\n excludedAttributes: CurrentRefinementsConnectorParams['excludedAttributes'];\n}): CurrentRefinementsConnectorParamsItem[] {\n const clearsQuery =\n (includedAttributes || []).indexOf('query') !== -1 ||\n (excludedAttributes || []).indexOf('query') === -1;\n\n const filterFunction = includedAttributes\n ? (item: CurrentRefinementsConnectorParamsRefinement) =>\n includedAttributes.indexOf(item.attribute) !== -1\n : (item: CurrentRefinementsConnectorParamsRefinement) =>\n excludedAttributes!.indexOf(item.attribute) === -1;\n\n const items = getRefinements(results, helper.state, clearsQuery)\n .map(normalizeRefinement)\n .filter(filterFunction);\n\n return items.reduce(\n (allItems, currentItem) => [\n ...allItems.filter(item => item.attribute !== currentItem.attribute),\n {\n indexName: helper.state.index,\n attribute: currentItem.attribute,\n label: currentItem.attribute,\n refinements: items\n .filter(result => result.attribute === currentItem.attribute)\n // We want to keep the order of refinements except the numeric ones.\n .sort((a, b) =>\n a.type === 'numeric' ? (a.value as number) - (b.value as number) : 0\n ),\n refine: refinement => clearRefinement(helper, refinement),\n },\n ],\n []\n );\n}\n\nfunction clearRefinementFromState(\n state: SearchParameters,\n refinement: CurrentRefinementsConnectorParamsRefinement\n): SearchParameters {\n switch (refinement.type) {\n case 'facet':\n return state.removeFacetRefinement(\n refinement.attribute,\n String(refinement.value)\n );\n case 'disjunctive':\n return state.removeDisjunctiveFacetRefinement(\n refinement.attribute,\n String(refinement.value)\n );\n case 'hierarchical':\n return state.removeHierarchicalFacetRefinement(refinement.attribute);\n case 'exclude':\n return state.removeExcludeRefinement(\n refinement.attribute,\n String(refinement.value)\n );\n case 'numeric':\n return state.removeNumericRefinement(\n refinement.attribute,\n refinement.operator,\n String(refinement.value)\n );\n case 'tag':\n return state.removeTagRefinement(String(refinement.value));\n case 'query':\n return state.setQueryParameter('query', '');\n default:\n warning(\n false,\n `The refinement type \"${refinement.type}\" does not exist and cannot be cleared from the current refinements.`\n );\n return state;\n }\n}\n\nfunction clearRefinement(\n helper: AlgoliaSearchHelper,\n refinement: CurrentRefinementsConnectorParamsRefinement\n): void {\n helper.setState(clearRefinementFromState(helper.state, refinement)).search();\n}\n\nfunction getOperatorSymbol(operator: SearchParameters.Operator): string {\n switch (operator) {\n case '>=':\n return '≥';\n case '<=':\n return '≤';\n default:\n return operator;\n }\n}\n\nfunction normalizeRefinement(\n refinement: Refinement\n): CurrentRefinementsConnectorParamsRefinement {\n const value =\n refinement.type === 'numeric' ? Number(refinement.name) : refinement.name;\n const label = (refinement as NumericRefinement).operator\n ? `${getOperatorSymbol(\n (refinement as NumericRefinement).operator as SearchParameters.Operator\n )} ${refinement.name}`\n : refinement.name;\n\n const normalizedRefinement: CurrentRefinementsConnectorParamsRefinement = {\n attribute: refinement.attribute,\n type: refinement.type,\n value,\n label,\n };\n\n if ((refinement as NumericRefinement).operator !== undefined) {\n normalizedRefinement.operator = (refinement as NumericRefinement).operator;\n }\n if ((refinement as FacetRefinement).count !== undefined) {\n normalizedRefinement.count = (refinement as FacetRefinement).count;\n }\n if ((refinement as FacetRefinement).exhaustive !== undefined) {\n normalizedRefinement.exhaustive = (refinement as FacetRefinement).exhaustive;\n }\n\n return normalizedRefinement;\n}\n\nexport default connectCurrentRefinements;\n","import {\n checkRendering,\n warning,\n createDocumentationMessageGenerator,\n createSendEventForFacet,\n isEqual,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'hierarchical-menu',\n connector: true,\n});\n\n/**\n * @typedef {Object} HierarchicalMenuItem\n * @property {string} value Value of the menu item.\n * @property {string} label Human-readable value of the menu item.\n * @property {number} count Number of matched results after refinement is applied.\n * @property {isRefined} boolean Indicates if the refinement is applied.\n * @property {Object} [data = undefined] n+1 level of items, same structure HierarchicalMenuItem (default: `undefined`).\n */\n\n/**\n * @typedef {Object} CustomHierarchicalMenuWidgetParams\n * @property {string[]} attributes Attributes to use to generate the hierarchy of the menu.\n * @property {string} [separator = '>'] Separator used in the attributes to separate level values.\n * @property {string} [rootPath = null] Prefix path to use if the first level is not the root level.\n * @property {boolean} [showParentLevel=false] Show the siblings of the selected parent levels of the current refined value. This\n * does not impact the root level.\n * @property {number} [limit = 10] Max number of values to display.\n * @property {boolean} [showMore = false] Whether to display the \"show more\" button.\n * @property {number} [showMoreLimit = 20] Max number of values to display when showing more.\n * @property {string[]|function} [sortBy = ['name:asc']] How to sort refinements. Possible values: `count|isRefined|name:asc|name:desc`.\n *\n * You can also use a sort function that behaves like the standard Javascript [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax).\n * @property {function(object[]):object[]} [transformItems] Function to transform the items passed to the templates.\n */\n\n/**\n * @typedef {Object} HierarchicalMenuRenderingOptions\n * @property {function(item.value): string} createURL Creates an url for the next state for a clicked item.\n * @property {HierarchicalMenuItem[]} items Values to be rendered.\n * @property {function(item.value)} refine Sets the path of the hierarchical filter and triggers a new search.\n * @property {Object} widgetParams All original `CustomHierarchicalMenuWidgetParams` forwarded to the `renderFn`.\n */\n\n/**\n * **HierarchicalMenu** connector provides the logic to build a custom widget\n * that will give the user the ability to explore facets in a tree-like structure.\n *\n * This is commonly used for multi-level categorization of products on e-commerce\n * websites. From a UX point of view, we suggest not displaying more than two\n * levels deep.\n *\n * @type {Connector}\n * @param {function(HierarchicalMenuRenderingOptions, boolean)} renderFn Rendering function for the custom **HierarchicalMenu** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomHierarchicalMenuWidgetParams)} Re-usable widget factory for a custom **HierarchicalMenu** widget.\n */\nexport default function connectHierarchicalMenu(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => {\n const {\n attributes,\n separator = ' > ',\n rootPath = null,\n showParentLevel = true,\n limit = 10,\n showMore = false,\n showMoreLimit = 20,\n sortBy = ['name:asc'],\n transformItems = items => items,\n } = widgetParams;\n\n if (!attributes || !Array.isArray(attributes) || attributes.length === 0) {\n throw new Error(\n withUsage('The `attributes` option expects an array of strings.')\n );\n }\n\n if (showMore === true && showMoreLimit <= limit) {\n throw new Error(\n withUsage('The `showMoreLimit` option must be greater than `limit`.')\n );\n }\n\n // we need to provide a hierarchicalFacet name for the search state\n // so that we can always map $hierarchicalFacetName => real attributes\n // we use the first attribute name\n const [hierarchicalFacetName] = attributes;\n let sendEvent;\n\n // Provide the same function to the `renderFn` so that way the user\n // has to only bind it once when `isFirstRendering` for instance\n let toggleShowMore = () => {};\n function cachedToggleShowMore() {\n toggleShowMore();\n }\n\n return {\n $$type: 'ais.hierarchicalMenu',\n\n isShowingMore: false,\n\n createToggleShowMore(renderOptions) {\n return () => {\n this.isShowingMore = !this.isShowingMore;\n this.render(renderOptions);\n };\n },\n\n getLimit() {\n return this.isShowingMore ? showMoreLimit : limit;\n },\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n _prepareFacetValues(facetValues) {\n return facetValues\n .slice(0, this.getLimit())\n .map(({ name: label, path: value, ...subValue }) => {\n if (Array.isArray(subValue.data)) {\n subValue.data = this._prepareFacetValues(subValue.data);\n }\n return { ...subValue, label, value };\n });\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n toggleShowMore = this.createToggleShowMore(renderOptions);\n\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n /**\n * @param {Object} param0 cleanup arguments\n * @param {any} param0.state current search parameters\n * @returns {any} next search parameters\n */\n dispose({ state }) {\n unmountFn();\n\n return state\n .removeHierarchicalFacet(hierarchicalFacetName)\n .setQueryParameter('maxValuesPerFacet', undefined);\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n hierarchicalMenu: {\n ...renderState.hierarchicalMenu,\n [hierarchicalFacetName]: this.getWidgetRenderState(renderOptions),\n },\n };\n },\n\n getWidgetRenderState({\n results,\n state,\n createURL,\n instantSearchInstance,\n helper,\n }) {\n // Bind createURL to this specific attribute\n function _createURL(facetValue) {\n return createURL(\n state.toggleRefinement(hierarchicalFacetName, facetValue)\n );\n }\n\n if (!sendEvent) {\n sendEvent = createSendEventForFacet({\n instantSearchInstance,\n helper,\n attribute: hierarchicalFacetName,\n widgetType: this.$$type,\n });\n }\n\n if (!this._refine) {\n this._refine = function(facetValue) {\n sendEvent('click', facetValue);\n helper.toggleRefinement(hierarchicalFacetName, facetValue).search();\n };\n }\n\n const facetValues = results\n ? results.getFacetValues(hierarchicalFacetName, { sortBy }).data || []\n : [];\n const items = transformItems(\n results ? this._prepareFacetValues(facetValues) : []\n );\n\n const getHasExhaustiveItems = () => {\n if (!results) {\n return false;\n }\n\n const currentLimit = this.getLimit();\n // If the limit is the max number of facet retrieved it is impossible to know\n // if the facets are exhaustive. The only moment we are sure it is exhaustive\n // is when it is strictly under the number requested unless we know that another\n // widget has requested more values (maxValuesPerFacet > getLimit()).\n // Because this is used for making the search of facets unable or not, it is important\n // to be conservative here.\n return state.maxValuesPerFacet > currentLimit\n ? facetValues.length <= currentLimit\n : facetValues.length < currentLimit;\n };\n\n return {\n items,\n refine: this._refine,\n createURL: _createURL,\n sendEvent,\n widgetParams,\n isShowingMore: this.isShowingMore,\n toggleShowMore: cachedToggleShowMore,\n canToggleShowMore:\n showMore && (this.isShowingMore || !getHasExhaustiveItems()),\n };\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const path = searchParameters.getHierarchicalFacetBreadcrumb(\n hierarchicalFacetName\n );\n\n if (!path.length) {\n return uiState;\n }\n\n return {\n ...uiState,\n hierarchicalMenu: {\n ...uiState.hierarchicalMenu,\n [hierarchicalFacetName]: path,\n },\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n const values =\n uiState.hierarchicalMenu &&\n uiState.hierarchicalMenu[hierarchicalFacetName];\n\n if (searchParameters.isHierarchicalFacet(hierarchicalFacetName)) {\n const facet = searchParameters.getHierarchicalFacetByName(\n hierarchicalFacetName\n );\n\n warning(\n isEqual(facet.attributes, attributes) &&\n facet.separator === separator &&\n facet.rootPath === rootPath,\n 'Using Breadcrumb and HierarchicalMenu on the same facet with different options overrides the configuration of the HierarchicalMenu.'\n );\n }\n\n const withFacetConfiguration = searchParameters\n .removeHierarchicalFacet(hierarchicalFacetName)\n .addHierarchicalFacet({\n name: hierarchicalFacetName,\n attributes,\n separator,\n rootPath,\n showParentLevel,\n });\n\n const currentMaxValuesPerFacet =\n withFacetConfiguration.maxValuesPerFacet || 0;\n\n const nextMaxValuesPerFacet = Math.max(\n currentMaxValuesPerFacet,\n showMore ? showMoreLimit : limit\n );\n\n const withMaxValuesPerFacet = withFacetConfiguration.setQueryParameter(\n 'maxValuesPerFacet',\n nextMaxValuesPerFacet\n );\n\n if (!values) {\n return withMaxValuesPerFacet.setQueryParameters({\n hierarchicalFacetsRefinements: {\n ...withMaxValuesPerFacet.hierarchicalFacetsRefinements,\n [hierarchicalFacetName]: [],\n },\n });\n }\n\n return withMaxValuesPerFacet.addHierarchicalFacetRefinement(\n hierarchicalFacetName,\n values.join(separator)\n );\n },\n };\n };\n}\n","import {\n escapeHits,\n TAG_PLACEHOLDER,\n checkRendering,\n createDocumentationMessageGenerator,\n addAbsolutePosition,\n addQueryID,\n createSendEventForHits,\n SendEventForHits,\n createBindEventForHits,\n BindEventForHits,\n noop,\n} from '../../lib/utils';\nimport { TransformItems, Connector, Hits, Hit, AlgoliaHit } from '../../types';\nimport { SearchResults } from 'algoliasearch-helper';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'hits',\n connector: true,\n});\n\nexport type HitsRendererOptions = {\n /**\n * The matched hits from Algolia API.\n */\n hits: Hits;\n\n /**\n * The response from the Algolia API.\n */\n results?: SearchResults;\n\n /**\n * Sends an event to the Insights middleware.\n */\n sendEvent: SendEventForHits;\n\n /**\n * Returns a string for the `data-insights-event` attribute for the Insights middleware\n */\n bindEvent: BindEventForHits;\n};\n\nexport type HitsConnectorParams = {\n /**\n * Whether to escape HTML tags from hits string values.\n *\n * @default true\n */\n escapeHTML?: boolean;\n\n /**\n * Function to transform the items passed to the templates.\n */\n transformItems?: TransformItems;\n};\n\nexport type HitsConnector = Connector;\n\nconst connectHits: HitsConnector = function connectHits(\n renderFn,\n unmountFn = noop\n) {\n checkRendering(renderFn, withUsage());\n\n return widgetParams => {\n const { escapeHTML = true, transformItems = items => items } =\n widgetParams || ({} as typeof widgetParams);\n let sendEvent;\n let bindEvent;\n\n return {\n $$type: 'ais.hits',\n\n init(initOptions) {\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance: initOptions.instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const renderState = this.getWidgetRenderState(renderOptions);\n renderState.sendEvent('view', renderState.hits);\n\n renderFn(\n {\n ...renderState,\n instantSearchInstance: renderOptions.instantSearchInstance,\n },\n false\n );\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n hits: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState({ results, helper, instantSearchInstance }) {\n if (!sendEvent) {\n sendEvent = createSendEventForHits({\n instantSearchInstance,\n index: helper.getIndex(),\n widgetType: this.$$type!,\n });\n }\n\n if (!bindEvent) {\n bindEvent = createBindEventForHits({\n index: helper.getIndex(),\n widgetType: this.$$type!,\n });\n }\n\n if (!results) {\n return {\n hits: [],\n results: undefined,\n sendEvent,\n bindEvent,\n widgetParams,\n };\n }\n\n if (escapeHTML && results.hits.length > 0) {\n results.hits = escapeHits(results.hits);\n }\n\n const initialEscaped = (results.hits as ReturnType)\n .__escaped;\n\n results.hits = addAbsolutePosition(\n results.hits,\n results.page,\n results.hitsPerPage\n );\n\n results.hits = addQueryID(results.hits, results.queryID);\n\n results.hits = transformItems(results.hits);\n\n // Make sure the escaped tag stays, even after mapping over the hits.\n // This prevents the hits from being double-escaped if there are multiple\n // hits widgets mounted on the page.\n (results.hits as ReturnType<\n typeof escapeHits\n >).__escaped = initialEscaped;\n\n return {\n hits: results.hits,\n results,\n sendEvent,\n bindEvent,\n widgetParams,\n };\n },\n\n dispose({ state }) {\n unmountFn();\n\n if (!escapeHTML) {\n return state;\n }\n\n return state.setQueryParameters(\n Object.keys(TAG_PLACEHOLDER).reduce(\n (acc, key) => ({\n ...acc,\n [key]: undefined,\n }),\n {}\n )\n );\n },\n\n getWidgetSearchParameters(state) {\n if (!escapeHTML) {\n return state;\n }\n\n return state.setQueryParameters(TAG_PLACEHOLDER);\n },\n };\n };\n};\n\nexport default connectHits;\n","import { SearchResults } from 'algoliasearch-helper';\nimport {\n uniq,\n find,\n createDocumentationMessageGenerator,\n warning,\n} from '../utils';\nimport {\n Hits,\n InsightsClient,\n InsightsClientMethod,\n InsightsClientPayload,\n InsightsClientWrapper,\n Connector,\n} from '../../types';\n\nconst getSelectedHits = (hits: Hits, selectedObjectIDs: string[]): Hits => {\n return selectedObjectIDs.map(objectID => {\n const hit = find(hits, h => h.objectID === objectID);\n if (typeof hit === 'undefined') {\n throw new Error(\n `Could not find objectID \"${objectID}\" passed to \\`clickedObjectIDsAfterSearch\\` in the returned hits. This is necessary to infer the absolute position and the query ID.`\n );\n }\n return hit;\n });\n};\n\nconst getQueryID = (selectedHits: Hits): string => {\n const queryIDs = uniq(selectedHits.map(hit => hit.__queryID));\n if (queryIDs.length > 1) {\n throw new Error(\n 'Insights currently allows a single `queryID`. The `objectIDs` provided map to multiple `queryID`s.'\n );\n }\n const queryID = queryIDs[0];\n if (typeof queryID !== 'string') {\n throw new Error(\n `Could not infer \\`queryID\\`. Ensure InstantSearch \\`clickAnalytics: true\\` was added with the Configure widget.\n\nSee: https://alg.li/lNiZZ7`\n );\n }\n return queryID;\n};\n\nconst getPositions = (selectedHits: Hits): number[] =>\n selectedHits.map(hit => hit.__position);\n\nexport const inferPayload = ({\n method,\n results,\n hits,\n objectIDs,\n}: {\n method: InsightsClientMethod;\n results: SearchResults;\n hits: Hits;\n objectIDs: string[];\n}): Omit => {\n const { index } = results;\n const selectedHits = getSelectedHits(hits, objectIDs);\n const queryID = getQueryID(selectedHits);\n\n switch (method) {\n case 'clickedObjectIDsAfterSearch': {\n const positions = getPositions(selectedHits);\n return { index, queryID, objectIDs, positions };\n }\n\n case 'convertedObjectIDsAfterSearch':\n return { index, queryID, objectIDs };\n\n default:\n throw new Error(`Unsupported method passed to insights: \"${method}\".`);\n }\n};\n\nconst wrapInsightsClient = (\n aa: InsightsClient | null,\n results: SearchResults,\n hits: Hits\n): InsightsClientWrapper => (\n method: InsightsClientMethod,\n payload: Partial\n) => {\n warning(\n false,\n `\\`insights\\` function has been deprecated. It is still supported in 4.x releases, but not further. It is replaced by the \\`insights\\` middleware.\n\nFor more information, visit https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-through-and-conversions/how-to/send-click-and-conversion-events-with-instantsearch/js/`\n );\n if (!aa) {\n const withInstantSearchUsage = createDocumentationMessageGenerator({\n name: 'instantsearch',\n });\n throw new Error(\n withInstantSearchUsage(\n 'The `insightsClient` option has not been provided to `instantsearch`.'\n )\n );\n }\n if (!Array.isArray(payload.objectIDs)) {\n throw new TypeError('Expected `objectIDs` to be an array.');\n }\n const inferredPayload = inferPayload({\n method,\n results,\n hits,\n objectIDs: payload.objectIDs,\n });\n aa(method, { ...inferredPayload, ...payload } as any);\n};\n\n/**\n * @deprecated This function will be still supported in 4.x releases, but not further. It is replaced by the `insights` middleware. For more information, visit https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-through-and-conversions/how-to/send-click-and-conversion-events-with-instantsearch/js/\n * It passes `insights` to `HitsWithInsightsListener` and `InfiniteHitsWithInsightsListener`.\n */\nexport default function withInsights(\n connector: Connector\n): Connector {\n const wrapRenderFn = renderFn => (renderOptions, isFirstRender) => {\n const { results, hits, instantSearchInstance } = renderOptions;\n if (results && hits && instantSearchInstance) {\n const insights = wrapInsightsClient(\n instantSearchInstance.insightsClient,\n results,\n hits\n );\n return renderFn({ ...renderOptions, insights }, isFirstRender);\n }\n return renderFn(renderOptions, isFirstRender);\n };\n\n return (renderFn, unmountFn) => connector(wrapRenderFn(renderFn), unmountFn);\n}\n","var n,l,u,t,i,r,o,f={},e=[],c=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i;function s(n,l){for(var u in l)n[u]=l[u];return n}function a(n){var l=n.parentNode;l&&l.removeChild(n)}function h(n,l,u){var t,i,r,o,f=arguments;if(l=s({},l),arguments.length>3)for(u=[u],t=3;t2&&(l.children=e.slice.call(arguments,2)),v(n.type,l,l.key||n.key,l.ref||n.ref)}function O(n){var l={},u={__c:\"__cC\"+o++,__p:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var t,i=this;return this.getChildContext||(t=[],this.getChildContext=function(){return l[u.__c]=i,l},this.shouldComponentUpdate=function(i){n.value!==i.value&&(l[u.__c].props.value=i.value,t.some(function(n){n.__P&&(n.context=i.value,k(n))}))},this.sub=function(n){t.push(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){t.splice(t.indexOf(n),1),l&&l.call(n)}}),n.children}};return u.Consumer.contextType=u,u}n={},l=function(n){return null!=n&&void 0===n.constructor},m.prototype.setState=function(n,l){var u=this.__s!==this.state&&this.__s||(this.__s=s({},this.state));(\"function\"!=typeof n||(n=n(u,this.props)))&&s(u,n),null!=n&&this.__v&&(this.u=!1,l&&this.__h.push(l),k(this))},m.prototype.forceUpdate=function(n){this.__v&&(n&&this.__h.push(n),this.u=!0,k(this))},m.prototype.render=d,u=[],t=\"function\"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,i=n.debounceRendering,n.__e=function(n,l,u){for(var t;l=l.__p;)if((t=l.__c)&&!t.__p)try{if(t.constructor&&null!=t.constructor.getDerivedStateFromError)t.setState(t.constructor.getDerivedStateFromError(n));else{if(null==t.componentDidCatch)continue;t.componentDidCatch(n)}return k(t.__E=t)}catch(l){n=l}throw n},r=f,o=0;export{I as render,L as hydrate,h as createElement,h,d as Fragment,p as createRef,l as isValidElement,m as Component,M as cloneElement,O as createContext,x as toChildArray,D as _unmount,n as options};\n//# sourceMappingURL=preact.module.js.map\n","/** @jsx h */\n\nimport { h } from 'preact';\nimport { readDataAttributes, hasDataAttributes } from '../../helpers/insights';\nimport { InsightsClientWrapper } from '../../types';\nimport { InsightsEvent } from '../../middlewares/createInsightsMiddleware';\n\ntype WithInsightsListenerProps = {\n [key: string]: unknown;\n insights: InsightsClientWrapper;\n sendEvent?: (event: InsightsEvent) => void;\n};\n\nconst findInsightsTarget = (\n startElement: HTMLElement | null,\n endElement: HTMLElement | null,\n validator: (element: HTMLElement) => boolean\n): HTMLElement | null => {\n let element: HTMLElement | null = startElement;\n while (element && !validator(element)) {\n if (element === endElement) {\n return null;\n }\n element = element.parentElement;\n }\n return element;\n};\n\nconst parseInsightsEvent = element => {\n const serializedPayload = element.getAttribute('data-insights-event');\n\n if (typeof serializedPayload !== 'string') {\n throw new Error(\n 'The insights middleware expects `data-insights-event` to be a base64-encoded JSON string.'\n );\n }\n\n try {\n return JSON.parse(atob(serializedPayload));\n } catch (error) {\n throw new Error(\n 'The insights middleware was unable to parse `data-insights-event`.'\n );\n }\n};\n\nconst insightsListener = (BaseComponent: any) => {\n function WithInsightsListener(props: WithInsightsListenerProps) {\n const handleClick = (event: MouseEvent): void => {\n if (props.sendEvent) {\n // new way with insights middleware\n const targetWithEvent = findInsightsTarget(\n event.target as HTMLElement | null,\n event.currentTarget as HTMLElement | null,\n element => element.hasAttribute('data-insights-event')\n );\n if (targetWithEvent) {\n const payload = parseInsightsEvent(targetWithEvent);\n props.sendEvent(payload);\n }\n }\n\n // old way, e.g. instantsearch.insights(\"clickedObjectIDsAfterSearch\", { .. })\n const insightsTarget = findInsightsTarget(\n event.target as HTMLElement | null,\n event.currentTarget as HTMLElement | null,\n element => hasDataAttributes(element)\n );\n if (insightsTarget) {\n const { method, payload } = readDataAttributes(insightsTarget);\n props.insights(method, payload);\n }\n };\n\n return (\n
\n \n
\n );\n }\n\n return WithInsightsListener;\n};\n\nexport default insightsListener;\n","import {\n checkRendering,\n warning,\n createDocumentationMessageGenerator,\n noop,\n} from '../../lib/utils';\n\nimport { AlgoliaSearchHelper, SearchParameters } from 'algoliasearch-helper';\nimport {\n Connector,\n TransformItems,\n CreateURL,\n InitOptions,\n RenderOptions,\n} from '../../types';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'hits-per-page',\n connector: true,\n});\n\nexport type HitsPerPageRendererOptionsItem = {\n /**\n * Label to display in the option.\n */\n label: string;\n\n /**\n * Number of hits to display per page.\n */\n value: number;\n\n /**\n * Indicates if it's the current refined value.\n */\n isRefined: boolean;\n};\n\nexport type HitsPerPageConnectorParamsItem = {\n /**\n * Label to display in the option.\n */\n label: string;\n\n /**\n * Number of hits to display per page.\n */\n value: number;\n\n /**\n * The default hits per page on first search.\n *\n * @default false\n */\n default?: boolean;\n};\n\nexport type HitsPerPageConnectorParams = {\n /**\n * Array of objects defining the different values and labels.\n */\n items: HitsPerPageConnectorParamsItem[];\n\n /**\n * Function to transform the items passed to the templates.\n */\n transformItems?: TransformItems;\n};\n\nexport type HitsPerPageRendererOptions = {\n /**\n * Array of objects defining the different values and labels.\n */\n items: HitsPerPageRendererOptionsItem[];\n\n /**\n * Creates the URL for a single item name in the list.\n *\n * @internal\n */\n createURL: CreateURL;\n\n /**\n * Sets the number of hits per page and triggers a search.\n */\n refine: (value: number) => void;\n\n /**\n * Indicates whether or not the search has results.\n */\n hasNoResults: boolean;\n};\n\nexport type HitsPerPageConnector = Connector<\n HitsPerPageRendererOptions,\n HitsPerPageConnectorParams\n>;\n\nconst connectHitsPerPage: HitsPerPageConnector = function connectHitsPerPage(\n renderFn,\n unmountFn = noop\n) {\n checkRendering(renderFn, withUsage());\n\n return widgetParams => {\n const { items: userItems, transformItems = items => items } =\n widgetParams || ({} as typeof widgetParams);\n let items = userItems;\n\n if (!Array.isArray(items)) {\n throw new Error(\n withUsage('The `items` option expects an array of objects.')\n );\n }\n\n const defaultItems = items.filter(item => item.default === true);\n\n if (defaultItems.length === 0) {\n throw new Error(\n withUsage(`A default value must be specified in \\`items\\`.`)\n );\n }\n\n if (defaultItems.length > 1) {\n throw new Error(\n withUsage('More than one default value is specified in `items`.')\n );\n }\n\n const defaultItem = defaultItems[0];\n\n const normalizeItems = ({ hitsPerPage }: SearchParameters) => {\n return items.map(item => ({\n ...item,\n isRefined: Number(item.value) === Number(hitsPerPage),\n }));\n };\n\n type ConnectorState = {\n getRefine: (\n helper: AlgoliaSearchHelper\n ) => (value: HitsPerPageConnectorParamsItem['value']) => any;\n createURLFactory: (props: {\n state: SearchParameters;\n createURL: (InitOptions | RenderOptions)['createURL'];\n }) => HitsPerPageRendererOptions['createURL'];\n };\n\n const connectorState: ConnectorState = {\n getRefine: helper => value => {\n return !value && value !== 0\n ? helper.setQueryParameter('hitsPerPage', undefined).search()\n : helper.setQueryParameter('hitsPerPage', value).search();\n },\n createURLFactory: ({ state, createURL }) => value =>\n createURL(\n state.setQueryParameter(\n 'hitsPerPage',\n !value && value !== 0 ? undefined : value\n )\n ),\n };\n\n return {\n $$type: 'ais.hitsPerPage',\n\n init(initOptions) {\n const { state, instantSearchInstance } = initOptions;\n\n const isCurrentInOptions = items.some(\n item => Number(state.hitsPerPage) === Number(item.value)\n );\n\n if (!isCurrentInOptions) {\n warning(\n state.hitsPerPage !== undefined,\n `\n\\`hitsPerPage\\` is not defined.\nThe option \\`hitsPerPage\\` needs to be set using the \\`configure\\` widget.\n\nLearn more: https://www.algolia.com/doc/api-reference/widgets/hits-per-page/js/\n `\n );\n\n warning(\n false,\n `\nThe \\`items\\` option of \\`hitsPerPage\\` does not contain the \"hits per page\" value coming from the state: ${state.hitsPerPage}.\n\nYou may want to add another entry to the \\`items\\` option with this value.`\n );\n\n items = [\n // The helper will convert the empty string to `undefined`.\n { value: ('' as unknown) as number, label: '' },\n ...items,\n ];\n }\n\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(initOptions) {\n const { instantSearchInstance } = initOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.setQueryParameter('hitsPerPage', undefined);\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n hitsPerPage: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState({ state, results, createURL, helper }) {\n return {\n items: transformItems(normalizeItems(state)),\n refine: connectorState.getRefine(helper),\n createURL: connectorState.createURLFactory({ state, createURL }),\n hasNoResults: results ? results.nbHits === 0 : true,\n widgetParams,\n };\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const hitsPerPage = searchParameters.hitsPerPage;\n\n if (hitsPerPage === undefined || hitsPerPage === defaultItem.value) {\n return uiState;\n }\n\n return {\n ...uiState,\n hitsPerPage,\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n return searchParameters.setQueryParameters({\n hitsPerPage: uiState.hitsPerPage || defaultItem.value,\n });\n },\n };\n };\n};\n\nexport default connectHitsPerPage;\n","import { withInsights } from '../../lib/insights';\nimport connectHits, {\n HitsRendererOptions,\n HitsConnectorParams,\n} from './connectHits';\nimport { Connector } from '../../types';\n\n/**\n * Due to https://github.com/microsoft/web-build-tools/issues/1050, we need\n * Connector<...> imported in this file, even though it is only used implicitly.\n * This _uses_ Connector<...> so it is not accidentally removed by someone.\n */\ndeclare type ImportWorkaround = Connector<\n HitsRendererOptions,\n HitsConnectorParams\n>;\n\nconst connectHitsWithInsights = withInsights(connectHits);\n\nexport default connectHitsWithInsights;\n","import {\n AlgoliaSearchHelper as Helper,\n SearchParameters,\n} from 'algoliasearch-helper';\nimport { Hits, Connector, TransformItems, Hit } from '../../types';\nimport {\n escapeHits,\n TAG_PLACEHOLDER,\n checkRendering,\n createDocumentationMessageGenerator,\n isEqual,\n addAbsolutePosition,\n addQueryID,\n noop,\n createSendEventForHits,\n SendEventForHits,\n createBindEventForHits,\n BindEventForHits,\n} from '../../lib/utils';\n\nexport type InfiniteHitsCachedHits = {\n [page: number]: Hits;\n};\n\ntype Read = ({\n state,\n}: {\n state: Partial;\n}) => InfiniteHitsCachedHits | null;\n\ntype Write = ({\n state,\n hits,\n}: {\n state: Partial;\n hits: InfiniteHitsCachedHits;\n}) => void;\n\nexport type InfiniteHitsCache = {\n read: Read;\n write: Write;\n};\n\nexport type InfiniteHitsConnectorParams = {\n /**\n * Escapes HTML entities from hits string values.\n *\n * @default `true`\n */\n escapeHTML?: boolean;\n\n /**\n * Enable the button to load previous results.\n *\n * @default `false`\n */\n showPrevious?: boolean;\n\n /**\n * Receives the items, and is called before displaying them.\n * Useful for mapping over the items to transform, and remove or reorder them.\n */\n transformItems?: TransformItems;\n\n /**\n * Reads and writes hits from/to cache.\n * When user comes back to the search page after leaving for product page,\n * this helps restore InfiniteHits and its scroll position.\n */\n cache?: InfiniteHitsCache;\n};\n\nexport type InfiniteHitsRendererOptions = {\n /**\n * Loads the previous results.\n */\n showPrevious: () => void;\n\n /**\n * Loads the next page of hits.\n */\n showMore: () => void;\n\n /**\n * Indicates whether the first page of hits has been reached.\n */\n isFirstPage: boolean;\n\n /**\n * Indicates whether the last page of hits has been reached.\n */\n isLastPage: boolean;\n\n /**\n * Send event to insights middleware\n */\n sendEvent: SendEventForHits;\n\n /**\n * Returns a string of data-insights-event attribute for insights middleware\n */\n bindEvent: BindEventForHits;\n\n /**\n * Hits for the current page\n */\n currentPageHits: Hits;\n};\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'infinite-hits',\n connector: true,\n});\n\nexport type InfiniteHitsConnector = Connector<\n InfiniteHitsRendererOptions,\n InfiniteHitsConnectorParams\n>;\n\nfunction getStateWithoutPage(state) {\n const { page, ...rest } = state || {};\n return rest;\n}\n\nfunction getInMemoryCache(): InfiniteHitsCache {\n let cachedHits: InfiniteHitsCachedHits | null = null;\n let cachedState = undefined;\n return {\n read({ state }) {\n return isEqual(cachedState, getStateWithoutPage(state))\n ? cachedHits\n : null;\n },\n write({ state, hits }) {\n cachedState = getStateWithoutPage(state);\n cachedHits = hits;\n },\n };\n}\n\nfunction extractHitsFromCachedHits(cachedHits: InfiniteHitsCachedHits) {\n return Object.keys(cachedHits)\n .map(Number)\n .sort((a, b) => a - b)\n .reduce((acc: Hits, page) => {\n return acc.concat(cachedHits[page]);\n }, []);\n}\n\nconst connectInfiniteHits: InfiniteHitsConnector = function connectInfiniteHits(\n renderFn,\n unmountFn = noop\n) {\n checkRendering(renderFn, withUsage());\n\n return widgetParams => {\n const {\n escapeHTML = true,\n transformItems = (items: any[]) => items,\n cache = getInMemoryCache(),\n } = widgetParams || ({} as typeof widgetParams);\n let showPrevious: () => void;\n let showMore: () => void;\n let sendEvent;\n let bindEvent;\n const getFirstReceivedPage = (\n state: SearchParameters,\n cachedHits: InfiniteHitsCachedHits\n ) => {\n const { page = 0 } = state;\n const pages = Object.keys(cachedHits).map(Number);\n if (pages.length === 0) {\n return page;\n } else {\n return Math.min(page, ...pages);\n }\n };\n const getLastReceivedPage = (\n state: SearchParameters,\n cachedHits: InfiniteHitsCachedHits\n ) => {\n const { page = 0 } = state;\n const pages = Object.keys(cachedHits).map(Number);\n if (pages.length === 0) {\n return page;\n } else {\n return Math.max(page, ...pages);\n }\n };\n\n const getShowPrevious = (\n helper: Helper,\n cachedHits: InfiniteHitsCachedHits\n ): (() => void) => () => {\n // Using the helper's `overrideStateWithoutTriggeringChangeEvent` method\n // avoid updating the browser URL when the user displays the previous page.\n helper\n .overrideStateWithoutTriggeringChangeEvent({\n ...helper.state,\n page: getFirstReceivedPage(helper.state, cachedHits) - 1,\n })\n .searchWithoutTriggeringOnStateChange();\n };\n const getShowMore = (\n helper: Helper,\n cachedHits: InfiniteHitsCachedHits\n ): (() => void) => () => {\n helper\n .setPage(getLastReceivedPage(helper.state, cachedHits) + 1)\n .search();\n };\n\n return {\n $$type: 'ais.infiniteHits',\n\n init(initOptions) {\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance: initOptions.instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n const widgetRenderState = this.getWidgetRenderState(renderOptions);\n\n sendEvent('view', widgetRenderState.currentPageHits);\n\n renderFn(\n {\n ...widgetRenderState,\n instantSearchInstance,\n },\n false\n );\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n infiniteHits: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState({ results, helper, state, instantSearchInstance }) {\n let isFirstPage: boolean;\n let currentPageHits: Hits = [];\n const cachedHits = cache.read({ state }) || {};\n\n if (!results) {\n showPrevious = getShowPrevious(helper, cachedHits);\n showMore = getShowMore(helper, cachedHits);\n sendEvent = createSendEventForHits({\n instantSearchInstance,\n index: helper.getIndex(),\n widgetType: this.$$type!,\n });\n bindEvent = createBindEventForHits({\n index: helper.getIndex(),\n widgetType: this.$$type!,\n });\n isFirstPage =\n helper.state.page === undefined ||\n getFirstReceivedPage(helper.state, cachedHits) === 0;\n } else {\n const { page = 0 } = state;\n\n if (escapeHTML && results.hits.length > 0) {\n results.hits = escapeHits(results.hits);\n }\n const initialEscaped = (results.hits as any).__escaped;\n\n results.hits = addAbsolutePosition(\n results.hits,\n results.page,\n results.hitsPerPage\n );\n\n results.hits = addQueryID(results.hits, results.queryID);\n\n results.hits = transformItems(results.hits);\n\n // Make sure the escaped tag stays after mapping over the hits.\n // This prevents the hits from being double-escaped if there are multiple\n // hits widgets mounted on the page.\n (results.hits as any).__escaped = initialEscaped;\n\n if (cachedHits[page] === undefined) {\n cachedHits[page] = results.hits;\n cache.write({ state, hits: cachedHits });\n }\n currentPageHits = results.hits;\n\n isFirstPage = getFirstReceivedPage(state, cachedHits) === 0;\n }\n\n const hits = extractHitsFromCachedHits(cachedHits);\n const isLastPage = results\n ? results.nbPages <= getLastReceivedPage(state, cachedHits) + 1\n : true;\n\n return {\n hits,\n currentPageHits,\n sendEvent,\n bindEvent,\n results,\n showPrevious,\n showMore,\n isFirstPage,\n isLastPage,\n widgetParams,\n };\n },\n\n dispose({ state }) {\n unmountFn();\n\n const stateWithoutPage = state.setQueryParameter('page', undefined);\n\n if (!escapeHTML) {\n return stateWithoutPage;\n }\n\n return stateWithoutPage.setQueryParameters(\n Object.keys(TAG_PLACEHOLDER).reduce(\n (acc, key) => ({\n ...acc,\n [key]: undefined,\n }),\n {}\n )\n );\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const page = searchParameters.page || 0;\n\n if (!page) {\n // return without adding `page` to uiState\n // because we don't want `page=1` in the URL\n return uiState;\n }\n\n return {\n ...uiState,\n // The page in the UI state is incremented by one\n // to expose the user value (not `0`).\n page: page + 1,\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n let widgetSearchParameters = searchParameters;\n\n if (escapeHTML) {\n widgetSearchParameters = searchParameters.setQueryParameters(\n TAG_PLACEHOLDER\n );\n }\n\n // The page in the search parameters is decremented by one\n // to get to the actual parameter value from the UI state.\n const page = uiState.page ? uiState.page - 1 : 0;\n\n return widgetSearchParameters.setQueryParameter('page', page);\n },\n };\n };\n};\n\nexport default connectInfiniteHits;\n","import { withInsights } from '../../lib/insights';\nimport connectInfiniteHits, {\n InfiniteHitsRendererOptions,\n InfiniteHitsConnectorParams,\n} from './connectInfiniteHits';\nimport { Connector } from '../../types';\n\n/**\n * Due to https://github.com/microsoft/web-build-tools/issues/1050, we need\n * Connector<...> imported in this file, even though it is only used implicitly.\n * This _uses_ Connector<...> so it is not accidentally removed by someone.\n */\ndeclare type ImportWorkaround = Connector<\n InfiniteHitsRendererOptions,\n InfiniteHitsConnectorParams\n>;\n\nconst connectInfiniteHitsWithInsights = withInsights(connectInfiniteHits);\n\nexport default connectInfiniteHitsWithInsights;\n","import {\n checkRendering,\n createDocumentationMessageGenerator,\n createSendEventForFacet,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'menu',\n connector: true,\n});\n\n/**\n * @typedef {Object} MenuItem\n * @property {string} value The value of the menu item.\n * @property {string} label Human-readable value of the menu item.\n * @property {number} count Number of results matched after refinement is applied.\n * @property {boolean} isRefined Indicates if the refinement is applied.\n */\n\n/**\n * @typedef {Object} CustomMenuWidgetParams\n * @property {string} attribute Name of the attribute for faceting (eg. \"free_shipping\").\n * @property {number} [limit = 10] How many facets values to retrieve.\n * @property {boolean} [showMore = false] Whether to display a button that expands the number of items.\n * @property {number} [showMoreLimit = 20] How many facets values to retrieve when `toggleShowMore` is called, this value is meant to be greater than `limit` option.\n * @property {string[]|function} [sortBy = ['isRefined', 'name:asc']] How to sort refinements. Possible values: `count|isRefined|name:asc|name:desc`.\n *\n * You can also use a sort function that behaves like the standard Javascript [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax).\n * @property {function(object[]):object[]} [transformItems] Function to transform the items passed to the templates.\n */\n\n/**\n * @typedef {Object} MenuRenderingOptions\n * @property {MenuItem[]} items The elements that can be refined for the current search results.\n * @property {function(item.value): string} createURL Creates the URL for a single item name in the list.\n * @property {function(item.value)} refine Filter the search to item value.\n * @property {boolean} canRefine True if refinement can be applied.\n * @property {Object} widgetParams All original `CustomMenuWidgetParams` forwarded to the `renderFn`.\n * @property {boolean} isShowingMore True if the menu is displaying all the menu items.\n * @property {function} toggleShowMore Toggles the number of values displayed between `limit` and `showMore.limit`.\n * @property {boolean} canToggleShowMore `true` if the toggleShowMore button can be activated (enough items to display more or\n * already displaying more than `limit` items)\n */\n\n/**\n * **Menu** connector provides the logic to build a widget that will give the user the ability to choose a single value for a specific facet. The typical usage of menu is for navigation in categories.\n *\n * This connector provides a `toggleShowMore()` function to display more or less items and a `refine()`\n * function to select an item. While selecting a new element, the `refine` will also unselect the\n * one that is currently selected.\n *\n * **Requirement:** the attribute passed as `attribute` must be present in \"attributes for faceting\" on the Algolia dashboard or configured as attributesForFaceting via a set settings call to the Algolia API.\n * @type {Connector}\n * @param {function(MenuRenderingOptions, boolean)} renderFn Rendering function for the custom **Menu** widget. widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomMenuWidgetParams)} Re-usable widget factory for a custom **Menu** widget.\n * @example\n * // custom `renderFn` to render the custom Menu widget\n * function renderFn(MenuRenderingOptions, isFirstRendering) {\n * if (isFirstRendering) {\n * MenuRenderingOptions.widgetParams.containerNode\n * .html('\n * \n * ${item.label} (${item.count})\n * \n * \n * `;\n * });\n *\n * RefinementListRenderingOptions.widgetParams.containerNode.find('ul').html(list);\n * RefinementListRenderingOptions.widgetParams.containerNode\n * .find('li[data-refine-value]')\n * .each(function() {\n * $(this).on('click', function(event) {\n * event.stopPropagation();\n * event.preventDefault();\n *\n * RefinementListRenderingOptions.refine($(this).data('refine-value'));\n * });\n * });\n * } else {\n * RefinementListRenderingOptions.widgetParams.containerNode.find('ul').html('');\n * }\n * }\n *\n * // connect `renderFn` to RefinementList logic\n * var customRefinementList = instantsearch.connectors.connectRefinementList(renderFn);\n *\n * // mount widget on the page\n * search.addWidgets([\n * customRefinementList({\n * containerNode: $('#custom-refinement-list-container'),\n * attribute: 'categories',\n * limit: 10,\n * })\n * ]);\n */\nexport default function connectRefinementList(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => {\n const {\n attribute,\n operator = 'or',\n limit = 10,\n showMore = false,\n showMoreLimit = 20,\n sortBy = ['isRefined', 'count:desc', 'name:asc'],\n escapeFacetValues = true,\n transformItems = items => items,\n } = widgetParams;\n\n if (!attribute) {\n throw new Error(withUsage('The `attribute` option is required.'));\n }\n\n if (!/^(and|or)$/.test(operator)) {\n throw new Error(\n withUsage(\n `The \\`operator\\` must one of: \\`\"and\"\\`, \\`\"or\"\\` (got \"${operator}\").`\n )\n );\n }\n\n if (showMore === true && showMoreLimit <= limit) {\n throw new Error(\n withUsage('`showMoreLimit` should be greater than `limit`.')\n );\n }\n\n const formatItems = ({ name: label, ...item }) => ({\n ...item,\n label,\n value: label,\n highlighted: label,\n });\n const getLimit = isShowingMore => (isShowingMore ? showMoreLimit : limit);\n\n let lastResultsFromMainSearch;\n let lastItemsFromMainSearch = [];\n let hasExhaustiveItems = true;\n let searchForFacetValues;\n let triggerRefine;\n let sendEvent;\n let toggleShowMore;\n\n /* eslint-disable max-params */\n const createSearchForFacetValues = function(helper) {\n return renderOptions => query => {\n const { instantSearchInstance } = renderOptions;\n if (query === '' && lastItemsFromMainSearch) {\n // render with previous data from the helper.\n renderFn({\n ...this.getWidgetRenderState({\n ...renderOptions,\n results: lastResultsFromMainSearch,\n }),\n instantSearchInstance,\n });\n } else {\n const tags = {\n highlightPreTag: escapeFacetValues\n ? TAG_PLACEHOLDER.highlightPreTag\n : TAG_REPLACEMENT.highlightPreTag,\n highlightPostTag: escapeFacetValues\n ? TAG_PLACEHOLDER.highlightPostTag\n : TAG_REPLACEMENT.highlightPostTag,\n };\n\n helper\n .searchForFacetValues(\n attribute,\n query,\n // We cap the `maxFacetHits` value to 100 because the Algolia API\n // doesn't support a greater number.\n // See https://www.algolia.com/doc/api-reference/api-parameters/maxFacetHits/\n Math.min(getLimit(this.isShowingMore), 100),\n tags\n )\n .then(results => {\n const facetValues = escapeFacetValues\n ? escapeFacets(results.facetHits)\n : results.facetHits;\n\n const normalizedFacetValues = transformItems(\n facetValues.map(({ value, ...item }) => ({\n ...item,\n value,\n label: value,\n }))\n );\n\n const canToggleShowMore =\n this.isShowingMore && lastItemsFromMainSearch.length > limit;\n\n renderFn({\n ...this.getWidgetRenderState({\n ...renderOptions,\n results: lastResultsFromMainSearch,\n }),\n items: normalizedFacetValues,\n canToggleShowMore,\n canRefine: true,\n instantSearchInstance,\n isFromSearch: true,\n });\n });\n }\n };\n };\n /* eslint-enable max-params */\n\n return {\n $$type: 'ais.refinementList',\n\n isShowingMore: false,\n\n // Provide the same function to the `renderFn` so that way the user\n // has to only bind it once when `isFirstRendering` for instance\n toggleShowMore() {},\n cachedToggleShowMore() {\n toggleShowMore();\n },\n\n createToggleShowMore(renderOptions) {\n return () => {\n this.isShowingMore = !this.isShowingMore;\n this.render(renderOptions);\n };\n },\n\n getLimit() {\n return getLimit(this.isShowingMore);\n },\n\n init(initOptions) {\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance: initOptions.instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance: renderOptions.instantSearchInstance,\n },\n false\n );\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n refinementList: {\n ...renderState.refinementList,\n [attribute]: this.getWidgetRenderState(renderOptions),\n },\n };\n },\n\n getWidgetRenderState(renderOptions) {\n const {\n results,\n state,\n createURL,\n instantSearchInstance,\n isFromSearch = false,\n helper,\n } = renderOptions;\n let items = [];\n let facetValues;\n\n if (!sendEvent || !triggerRefine || !searchForFacetValues) {\n sendEvent = createSendEventForFacet({\n instantSearchInstance,\n helper,\n attribute,\n widgetType: this.$$type,\n });\n\n triggerRefine = facetValue => {\n sendEvent('click', facetValue);\n helper.toggleRefinement(attribute, facetValue).search();\n };\n\n searchForFacetValues = createSearchForFacetValues.call(this, helper);\n }\n\n if (results) {\n if (!isFromSearch) {\n facetValues = results.getFacetValues(attribute, { sortBy }) || [];\n items = transformItems(\n facetValues.slice(0, this.getLimit()).map(formatItems)\n );\n } else {\n facetValues = escapeFacetValues\n ? escapeFacets(results.facetHits)\n : results.facetHits;\n\n items = transformItems(\n facetValues.map(({ value, ...item }) => ({\n ...item,\n value,\n label: value,\n }))\n );\n }\n\n const maxValuesPerFacetConfig = state.maxValuesPerFacet;\n const currentLimit = this.getLimit();\n // If the limit is the max number of facet retrieved it is impossible to know\n // if the facets are exhaustive. The only moment we are sure it is exhaustive\n // is when it is strictly under the number requested unless we know that another\n // widget has requested more values (maxValuesPerFacet > getLimit()).\n // Because this is used for making the search of facets unable or not, it is important\n // to be conservative here.\n hasExhaustiveItems =\n maxValuesPerFacetConfig > currentLimit\n ? facetValues.length <= currentLimit\n : facetValues.length < currentLimit;\n\n lastResultsFromMainSearch = results;\n lastItemsFromMainSearch = items;\n\n toggleShowMore = this.createToggleShowMore(renderOptions);\n }\n\n // Compute a specific createURL method able to link to any facet value state change\n const _createURL = facetValue =>\n createURL(state.toggleRefinement(attribute, facetValue));\n\n // Do not mistake searchForFacetValues and searchFacetValues which is the actual search\n // function\n const searchFacetValues =\n searchForFacetValues && searchForFacetValues(renderOptions);\n\n const canShowLess =\n this.isShowingMore && lastItemsFromMainSearch.length > limit;\n const canShowMore = showMore && !isFromSearch && !hasExhaustiveItems;\n\n const canToggleShowMore = canShowLess || canShowMore;\n\n return {\n createURL: _createURL,\n items,\n refine: triggerRefine,\n searchForItems: searchFacetValues,\n isFromSearch,\n canRefine: isFromSearch || items.length > 0,\n widgetParams,\n isShowingMore: this.isShowingMore,\n canToggleShowMore,\n toggleShowMore: this.cachedToggleShowMore,\n sendEvent,\n hasExhaustiveItems,\n };\n },\n\n dispose({ state }) {\n unmountFn();\n\n const withoutMaxValuesPerFacet = state.setQueryParameter(\n 'maxValuesPerFacet',\n undefined\n );\n if (operator === 'and') {\n return withoutMaxValuesPerFacet.removeFacet(attribute);\n }\n return withoutMaxValuesPerFacet.removeDisjunctiveFacet(attribute);\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const values =\n operator === 'or'\n ? searchParameters.getDisjunctiveRefinements(attribute)\n : searchParameters.getConjunctiveRefinements(attribute);\n\n if (!values.length) {\n return uiState;\n }\n\n return {\n ...uiState,\n refinementList: {\n ...uiState.refinementList,\n [attribute]: values,\n },\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n const isDisjunctive = operator === 'or';\n const values =\n uiState.refinementList && uiState.refinementList[attribute];\n\n const withoutRefinements = searchParameters.clearRefinements(attribute);\n const withFacetConfiguration = isDisjunctive\n ? withoutRefinements.addDisjunctiveFacet(attribute)\n : withoutRefinements.addFacet(attribute);\n\n const currentMaxValuesPerFacet =\n withFacetConfiguration.maxValuesPerFacet || 0;\n\n const nextMaxValuesPerFacet = Math.max(\n currentMaxValuesPerFacet,\n showMore ? showMoreLimit : limit\n );\n\n const withMaxValuesPerFacet = withFacetConfiguration.setQueryParameter(\n 'maxValuesPerFacet',\n nextMaxValuesPerFacet\n );\n\n if (!values) {\n const key = isDisjunctive\n ? 'disjunctiveFacetsRefinements'\n : 'facetsRefinements';\n\n return withMaxValuesPerFacet.setQueryParameters({\n [key]: {\n ...withMaxValuesPerFacet[key],\n [attribute]: [],\n },\n });\n }\n\n return values.reduce(\n (parameters, value) =>\n isDisjunctive\n ? parameters.addDisjunctiveFacetRefinement(attribute, value)\n : parameters.addFacetRefinement(attribute, value),\n withMaxValuesPerFacet\n );\n },\n };\n };\n}\n","import {\n checkRendering,\n createDocumentationMessageGenerator,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'search-box',\n connector: true,\n});\n\n/**\n * @typedef {Object} CustomSearchBoxWidgetParams\n * @property {function(string, function(string))} [queryHook = undefined] A function that will be called every time\n * a new value for the query is set. The first parameter is the query and the second is a\n * function to actually trigger the search. The function takes the query as the parameter.\n *\n * This queryHook can be used to debounce the number of searches done from the searchBox.\n */\n\n/**\n * @typedef {Object} SearchBoxRenderingOptions\n * @property {string} query The query from the last search.\n * @property {function(string)} refine Sets a new query and searches.\n * @property {function()} clear Remove the query and perform search.\n * @property {Object} widgetParams All original `CustomSearchBoxWidgetParams` forwarded to the `renderFn`.\n * @property {boolean} isSearchStalled `true` if the search results takes more than a certain time to come back\n * from Algolia servers. This can be configured on the InstantSearch constructor with the attribute\n * `stalledSearchDelay` which is 200ms, by default.\n */\n\n/**\n * **SearchBox** connector provides the logic to build a widget that will let the user search for a query.\n *\n * The connector provides to the rendering: `refine()` to set the query. The behaviour of this function\n * may be impacted by the `queryHook` widget parameter.\n * @type {Connector}\n * @param {function(SearchBoxRenderingOptions, boolean)} renderFn Rendering function for the custom **SearchBox** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomSearchBoxWidgetParams)} Re-usable widget factory for a custom **SearchBox** widget.\n * @example\n * // custom `renderFn` to render the custom SearchBox widget\n * function renderFn(SearchBoxRenderingOptions, isFirstRendering) {\n * if (isFirstRendering) {\n * SearchBoxRenderingOptions.widgetParams.containerNode.html('');\n * SearchBoxRenderingOptions.widgetParams.containerNode\n * .find('input')\n * .on('keyup', function() {\n * SearchBoxRenderingOptions.refine($(this).val());\n * });\n * SearchBoxRenderingOptions.widgetParams.containerNode\n * .find('input')\n * .val(SearchBoxRenderingOptions.query);\n * }\n * }\n *\n * // connect `renderFn` to SearchBox logic\n * var customSearchBox = instantsearch.connectors.connectSearchBox(renderFn);\n *\n * // mount widget on the page\n * search.addWidgets([\n * customSearchBox({\n * containerNode: $('#custom-searchbox'),\n * })\n * ]);\n */\nexport default function connectSearchBox(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => {\n const { queryHook } = widgetParams;\n\n function clear(helper) {\n return function() {\n helper.setQuery('').search();\n };\n }\n\n let _clear = () => {};\n function _cachedClear() {\n _clear();\n }\n\n return {\n $$type: 'ais.searchBox',\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.setQueryParameter('query', undefined);\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n searchBox: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState({ helper, searchMetadata }) {\n if (!this._refine) {\n const setQueryAndSearch = query => {\n if (query !== helper.state.query) {\n helper.setQuery(query).search();\n }\n };\n\n this._refine = query => {\n if (queryHook) {\n queryHook(query, setQueryAndSearch);\n return;\n }\n\n setQueryAndSearch(query);\n };\n }\n\n _clear = clear(helper);\n\n return {\n query: helper.state.query || '',\n refine: this._refine,\n clear: _cachedClear,\n widgetParams,\n isSearchStalled: searchMetadata.isSearchStalled,\n };\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const query = searchParameters.query || '';\n\n if (query === '' || (uiState && uiState.query === query)) {\n return uiState;\n }\n\n return {\n ...uiState,\n query,\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n return searchParameters.setQueryParameter('query', uiState.query || '');\n },\n };\n };\n}\n","import {\n checkRendering,\n createDocumentationMessageGenerator,\n find,\n warning,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'sort-by',\n connector: true,\n});\n\n/**\n * @typedef {Object} SortByItem\n * @property {string} value The name of the index to target.\n * @property {string} label The label of the index to display.\n */\n\n/**\n * @typedef {Object} CustomSortByWidgetParams\n * @property {SortByItem[]} items Array of objects defining the different indices to choose from.\n * @property {function(object[]):object[]} [transformItems] Function to transform the items passed to the templates.\n */\n\n/**\n * @typedef {Object} SortByRenderingOptions\n * @property {string} currentRefinement The currently selected index.\n * @property {SortByItem[]} options All the available indices\n * @property {function(string)} refine Switches indices and triggers a new search.\n * @property {boolean} hasNoResults `true` if the last search contains no result.\n * @property {Object} widgetParams All original `CustomSortByWidgetParams` forwarded to the `renderFn`.\n */\n\n/**\n * The **SortBy** connector provides the logic to build a custom widget that will display a\n * list of indices. With Algolia, this is most commonly used for changing ranking strategy. This allows\n * a user to change how the hits are being sorted.\n *\n * This connector provides the `refine` function that allows to switch indices.\n * The connector provides to the rendering: `refine()` to switch the current index and\n * `options` that are the values that can be selected. `refine` should be used\n * with `options.value`.\n * @type {Connector}\n * @param {function(SortByRenderingOptions, boolean)} renderFn Rendering function for the custom **SortBy** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomSortByWidgetParams)} Re-usable widget factory for a custom **SortBy** widget.\n * @example\n * // custom `renderFn` to render the custom SortBy widget\n * function renderFn(SortByRenderingOptions, isFirstRendering) {\n * if (isFirstRendering) {\n * SortByRenderingOptions.widgetParams.containerNode.html('');\n * SortByRenderingOptions.widgetParams.containerNode\n * .find('select')\n * .on('change', function(event) {\n * SortByRenderingOptions.refine(event.target.value);\n * });\n * }\n *\n * var optionsHTML = SortByRenderingOptions.options.map(function(option) {\n * return `\n * \n * ${option.label}\n * \n * `;\n * });\n *\n * SortByRenderingOptions.widgetParams.containerNode\n * .find('select')\n * .html(optionsHTML);\n * }\n *\n * // connect `renderFn` to SortBy logic\n * var customSortBy = instantsearch.connectors.connectSortBy(renderFn);\n *\n * // mount widget on the page\n * search.addWidgets([\n * customSortBy({\n * containerNode: $('#custom-sort-by-container'),\n * items: [\n * { value: 'instant_search', label: 'Most relevant' },\n * { value: 'instant_search_price_asc', label: 'Lowest price' },\n * { value: 'instant_search_price_desc', label: 'Highest price' },\n * ],\n * })\n * ]);\n */\nexport default function connectSortBy(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => {\n const { items, transformItems = x => x } = widgetParams;\n\n if (!Array.isArray(items)) {\n throw new Error(\n withUsage('The `items` option expects an array of objects.')\n );\n }\n\n return {\n $$type: 'ais.sortBy',\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n\n const widgetRenderState = this.getWidgetRenderState(initOptions);\n const currentIndex = widgetRenderState.currentRefinement;\n const isCurrentIndexInItems = find(\n items,\n item => item.value === currentIndex\n );\n\n warning(\n isCurrentIndexInItems,\n `The index named \"${currentIndex}\" is not listed in the \\`items\\` of \\`sortBy\\`.`\n );\n\n renderFn(\n {\n ...widgetRenderState,\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.setIndex(this.initialIndex);\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n sortBy: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState({ results, helper, parent }) {\n if (!this.initialIndex) {\n this.initialIndex = parent.getIndexName();\n }\n if (!this.setIndex) {\n this.setIndex = indexName => {\n helper.setIndex(indexName).search();\n };\n }\n\n return {\n currentRefinement: helper.state.index,\n options: transformItems(items),\n refine: this.setIndex,\n hasNoResults: results ? results.nbHits === 0 : true,\n widgetParams,\n };\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const currentIndex = searchParameters.index;\n const isInitialIndex = currentIndex === this.initialIndex;\n\n if (isInitialIndex) {\n return uiState;\n }\n\n return {\n ...uiState,\n sortBy: currentIndex,\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n return searchParameters.setQueryParameter(\n 'index',\n uiState.sortBy || this.initialIndex || searchParameters.index\n );\n },\n };\n };\n}\n","import {\n checkRendering,\n createDocumentationLink,\n createDocumentationMessageGenerator,\n noop,\n warning,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'rating-menu',\n connector: true,\n});\n\nconst $$type = 'ais.ratingMenu';\n\nconst MAX_VALUES_PER_FACET_API_LIMIT = 1000;\nconst STEP = 1;\n\nconst createSendEvent = ({\n instantSearchInstance,\n helper,\n getRefinedStar,\n attribute,\n}) => (...args) => {\n if (args.length === 1) {\n instantSearchInstance.sendEventToInsights(args[0]);\n return;\n }\n const [eventType, facetValue, eventName = 'Filter Applied'] = args;\n if (eventType !== 'click') {\n return;\n }\n const isRefined = getRefinedStar() === Number(facetValue);\n if (!isRefined) {\n instantSearchInstance.sendEventToInsights({\n insightsMethod: 'clickedFilters',\n widgetType: $$type,\n eventType,\n payload: {\n eventName,\n index: helper.getIndex(),\n filters: [`${attribute}>=${facetValue}`],\n },\n });\n }\n};\n\n/**\n * @typedef {Object} StarRatingItems\n * @property {string} name Name corresponding to the number of stars.\n * @property {string} value Number of stars as string.\n * @property {number} count Count of matched results corresponding to the number of stars.\n * @property {boolean[]} stars Array of length of maximum rating value with stars to display or not.\n * @property {boolean} isRefined Indicates if star rating refinement is applied.\n */\n\n/**\n * @typedef {Object} CustomStarRatingWidgetParams\n * @property {string} attribute Name of the attribute for faceting (eg. \"free_shipping\").\n * @property {number} [max = 5] The maximum rating value.\n */\n\n/**\n * @typedef {Object} StarRatingRenderingOptions\n * @property {StarRatingItems[]} items Possible star ratings the user can apply.\n * @property {function(string): string} createURL Creates an URL for the next\n * state (takes the item value as parameter). Takes the value of an item as parameter.\n * @property {function(string)} refine Selects a rating to filter the results\n * (takes the filter value as parameter). Takes the value of an item as parameter.\n * @property {boolean} hasNoResults `true` if the last search contains no result.\n * @property {Object} widgetParams All original `CustomStarRatingWidgetParams` forwarded to the `renderFn`.\n */\n\n/**\n * **StarRating** connector provides the logic to build a custom widget that will let\n * the user refine search results based on ratings.\n *\n * The connector provides to the rendering: `refine()` to select a value and\n * `items` that are the values that can be selected. `refine` should be used\n * with `items.value`.\n * @type {Connector}\n * @param {function(StarRatingRenderingOptions, boolean)} renderFn Rendering function for the custom **StarRating** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomStarRatingWidgetParams)} Re-usable widget factory for a custom **StarRating** widget.\n * @example\n * // custom `renderFn` to render the custom StarRating widget\n * function renderFn(StarRatingRenderingOptions, isFirstRendering) {\n * if (isFirstRendering) {\n * StarRatingRenderingOptions.widgetParams.containerNode.html('
    ');\n * }\n *\n * StarRatingRenderingOptions.widgetParams.containerNode\n * .find('li[data-refine-value]')\n * .each(function() { $(this).off('click'); });\n *\n * var listHTML = StarRatingRenderingOptions.items.map(function(item) {\n * return '
  • ' +\n * '' +\n * item.stars.map(function(star) { return star === false ? '☆' : '★'; }).join(' ') +\n * '& up (' + item.count + ')' +\n * '
  • ';\n * });\n *\n * StarRatingRenderingOptions.widgetParams.containerNode\n * .find('ul')\n * .html(listHTML);\n *\n * StarRatingRenderingOptions.widgetParams.containerNode\n * .find('li[data-refine-value]')\n * .each(function() {\n * $(this).on('click', function(event) {\n * event.preventDefault();\n * event.stopPropagation();\n *\n * StarRatingRenderingOptions.refine($(this).data('refine-value'));\n * });\n * });\n * }\n *\n * // connect `renderFn` to StarRating logic\n * var customStarRating = instantsearch.connectors.connectRatingMenu(renderFn);\n *\n * // mount widget on the page\n * search.addWidgets([\n * customStarRating({\n * containerNode: $('#custom-rating-menu-container'),\n * attribute: 'rating',\n * max: 5,\n * })\n * ]);\n */\nexport default function connectRatingMenu(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => {\n const { attribute, max = 5 } = widgetParams;\n let sendEvent;\n\n if (!attribute) {\n throw new Error(withUsage('The `attribute` option is required.'));\n }\n\n const getRefinedStar = state => {\n const values = state.getNumericRefinements(attribute);\n\n if (!values['>=']?.length) {\n return undefined;\n }\n\n return values['>='][0];\n };\n\n const getFacetsMaxDecimalPlaces = facetResults => {\n let maxDecimalPlaces = 0;\n facetResults.forEach(facetResult => {\n const [, decimal = ''] = facetResult.name.split('.');\n maxDecimalPlaces = Math.max(maxDecimalPlaces, decimal.length);\n });\n return maxDecimalPlaces;\n };\n\n const getFacetValuesWarningMessage = ({\n maxDecimalPlaces,\n maxFacets,\n maxValuesPerFacet,\n }) => {\n const maxDecimalPlacesInRange = Math.max(\n 0,\n Math.floor(Math.log10(MAX_VALUES_PER_FACET_API_LIMIT / max))\n );\n const maxFacetsInRange = Math.min(\n MAX_VALUES_PER_FACET_API_LIMIT,\n Math.pow(10, maxDecimalPlacesInRange) * max\n );\n\n const solutions = [];\n\n if (maxFacets > MAX_VALUES_PER_FACET_API_LIMIT) {\n solutions.push(\n `- Update your records to lower the precision of the values in the \"${attribute}\" attribute (for example: ${(5.123456789).toPrecision(\n maxDecimalPlaces + 1\n )} to ${(5.123456789).toPrecision(maxDecimalPlacesInRange + 1)})`\n );\n }\n if (maxValuesPerFacet < maxFacetsInRange) {\n solutions.push(\n `- Increase the maximum number of facet values to ${maxFacetsInRange} using the \"configure\" widget ${createDocumentationLink(\n { name: 'configure' }\n )} and the \"maxValuesPerFacet\" parameter https://www.algolia.com/doc/api-reference/api-parameters/maxValuesPerFacet/`\n );\n }\n\n return `The ${attribute} attribute can have ${maxFacets} different values (0 to ${max} with a maximum of ${maxDecimalPlaces} decimals = ${maxFacets}) but you retrieved only ${maxValuesPerFacet} facet values. Therefore the number of results that match the refinements can be incorrect.\n${\n solutions.length\n ? `To resolve this problem you can:\\n${solutions.join('\\n')}`\n : ``\n}`;\n };\n\n const toggleRefinement = (helper, facetValue) => {\n sendEvent('click', facetValue);\n const isRefined = getRefinedStar(helper.state) === Number(facetValue);\n helper.removeNumericRefinement(attribute);\n if (!isRefined) {\n helper\n .addNumericRefinement(attribute, '<=', max)\n .addNumericRefinement(attribute, '>=', facetValue);\n }\n helper.search();\n };\n\n const connectorState = {\n toggleRefinementFactory: helper => toggleRefinement.bind(this, helper),\n createURLFactory: ({ state, createURL }) => value =>\n createURL(\n state\n .removeNumericRefinement(attribute)\n .addNumericRefinement(attribute, '<=', max)\n .addNumericRefinement(attribute, '>=', value)\n ),\n };\n\n return {\n $$type,\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n ratingMenu: {\n ...renderState.ratingMenu,\n [attribute]: this.getWidgetRenderState(renderOptions),\n },\n };\n },\n\n getWidgetRenderState({\n helper,\n results,\n state,\n instantSearchInstance,\n createURL,\n }) {\n let facetValues = [];\n\n if (!sendEvent) {\n sendEvent = createSendEvent({\n instantSearchInstance,\n helper,\n getRefinedStar: () => getRefinedStar(helper.state),\n attribute,\n });\n }\n\n if (results) {\n const facetResults = results.getFacetValues(attribute);\n const maxValuesPerFacet = facetResults.length;\n\n const maxDecimalPlaces = getFacetsMaxDecimalPlaces(facetResults);\n const maxFacets = Math.pow(10, maxDecimalPlaces) * max;\n\n warning(\n maxFacets <= maxValuesPerFacet,\n getFacetValuesWarningMessage({\n maxDecimalPlaces,\n maxFacets,\n maxValuesPerFacet,\n })\n );\n\n const refinedStar = getRefinedStar(state);\n\n for (let star = STEP; star < max; star += STEP) {\n const isRefined = refinedStar === star;\n\n const count = facetResults\n .filter(f => Number(f.name) >= star && Number(f.name) <= max)\n .map(f => f.count)\n .reduce((sum, current) => sum + current, 0);\n\n if (refinedStar && !isRefined && count === 0) {\n // skip count==0 when at least 1 refinement is enabled\n // eslint-disable-next-line no-continue\n continue;\n }\n\n const stars = [...new Array(Math.floor(max / STEP))].map(\n (v, i) => i * STEP < star\n );\n\n facetValues.push({\n stars,\n name: String(star),\n value: String(star),\n count,\n isRefined,\n });\n }\n }\n facetValues = facetValues.reverse();\n\n return {\n items: facetValues,\n hasNoResults: results ? results.nbHits === 0 : true,\n refine: connectorState.toggleRefinementFactory(helper),\n sendEvent,\n createURL: connectorState.createURLFactory({ state, createURL }),\n widgetParams,\n };\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.removeNumericRefinement(attribute);\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const value = getRefinedStar(searchParameters);\n\n if (typeof value !== 'number') {\n return uiState;\n }\n\n return {\n ...uiState,\n ratingMenu: {\n ...uiState.ratingMenu,\n [attribute]: value,\n },\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n const value = uiState.ratingMenu && uiState.ratingMenu[attribute];\n\n const withoutRefinements = searchParameters.clearRefinements(attribute);\n const withDisjunctiveFacet = withoutRefinements.addDisjunctiveFacet(\n attribute\n );\n\n if (!value) {\n return withDisjunctiveFacet.setQueryParameters({\n numericRefinements: {\n ...withDisjunctiveFacet.numericRefinements,\n [attribute]: [],\n },\n });\n }\n\n return withDisjunctiveFacet\n .addNumericRefinement(attribute, '<=', max)\n .addNumericRefinement(attribute, '>=', value);\n },\n };\n };\n}\n","import {\n checkRendering,\n createDocumentationMessageGenerator,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'stats',\n connector: true,\n});\n\n/**\n * @typedef {Object} StatsRenderingOptions\n * @property {number} hitsPerPage The maximum number of hits per page returned by Algolia.\n * @property {number} nbHits The number of hits in the result set.\n * @property {number} nbPages The number of pages computed for the result set.\n * @property {number} page The current page.\n * @property {number} processingTimeMS The time taken to compute the results inside the Algolia engine.\n * @property {string} query The query used for the current search.\n * @property {object} widgetParams All original `CustomStatsWidgetParams` forwarded to the `renderFn`.\n */\n\n/**\n * @typedef {Object} CustomStatsWidgetParams\n */\n\n/**\n * **Stats** connector provides the logic to build a custom widget that will displays\n * search statistics (hits number and processing time).\n *\n * @type {Connector}\n * @param {function(StatsRenderingOptions, boolean)} renderFn Rendering function for the custom **Stats** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomStatsWidgetParams)} Re-usable widget factory for a custom **Stats** widget.\n * @example\n * // custom `renderFn` to render the custom Stats widget\n * function renderFn(StatsRenderingOptions, isFirstRendering) {\n * if (isFirstRendering) return;\n *\n * StatsRenderingOptions.widgetParams.containerNode\n * .html(StatsRenderingOptions.nbHits + ' results found in ' + StatsRenderingOptions.processingTimeMS);\n * }\n *\n * // connect `renderFn` to Stats logic\n * var customStatsWidget = instantsearch.connectors.connectStats(renderFn);\n *\n * // mount widget on the page\n * search.addWidgets([\n * customStatsWidget({\n * containerNode: $('#custom-stats-container'),\n * })\n * ]);\n */\nexport default function connectStats(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => ({\n $$type: 'ais.stats',\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n dispose() {\n unmountFn();\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n stats: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState({ results, helper }) {\n if (!results) {\n return {\n hitsPerPage: helper.state.hitsPerPage,\n nbHits: 0,\n nbPages: 0,\n page: helper.state.page || 0,\n processingTimeMS: -1,\n query: helper.state.query || '',\n widgetParams,\n };\n }\n\n return {\n hitsPerPage: results.hitsPerPage,\n nbHits: results.nbHits,\n nbPages: results.nbPages,\n page: results.page,\n processingTimeMS: results.processingTimeMS,\n query: results.query,\n widgetParams,\n };\n },\n });\n}\n","import {\n checkRendering,\n escapeRefinement,\n unescapeRefinement,\n createDocumentationMessageGenerator,\n find,\n noop,\n toArray,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'toggle-refinement',\n connector: true,\n});\n\nconst $$type = 'ais.toggleRefinement';\n\nconst createSendEvent = ({ instantSearchInstance, attribute, on, helper }) => (\n ...args\n) => {\n if (args.length === 1) {\n instantSearchInstance.sendEventToInsights(args[0]);\n return;\n }\n const [eventType, isRefined, eventName = 'Filter Applied'] = args;\n if (eventType !== 'click' || on === undefined) {\n return;\n }\n // Checking\n if (!isRefined) {\n instantSearchInstance.sendEventToInsights({\n insightsMethod: 'clickedFilters',\n widgetType: $$type,\n eventType,\n payload: {\n eventName,\n index: helper.getIndex(),\n filters: on.map(value => `${attribute}:${value}`),\n },\n });\n }\n};\n\n/**\n * @typedef {Object} ToggleValue\n * @property {boolean} isRefined `true` if the toggle is on.\n * @property {number} count Number of results matched after applying the toggle refinement.\n * @property {Object} onFacetValue Value of the toggle when it's on.\n * @property {Object} offFacetValue Value of the toggle when it's off.\n */\n\n/**\n * @typedef {Object} CustomToggleWidgetParams\n * @property {string} attribute Name of the attribute for faceting (eg. \"free_shipping\").\n * @property {Object} [on = true] Value to filter on when toggled.\n * @property {Object} [off] Value to filter on when not toggled.\n */\n\n/**\n * @typedef {Object} ToggleRenderingOptions\n * @property {ToggleValue} value The current toggle value.\n * @property {function():string} createURL Creates an URL for the next state.\n * @property {function(value)} refine Updates to the next state by applying the toggle refinement.\n * @property {Object} widgetParams All original `CustomToggleWidgetParams` forwarded to the `renderFn`.\n */\n\n/**\n * **Toggle** connector provides the logic to build a custom widget that will provide\n * an on/off filtering feature based on an attribute value or values.\n *\n * Two modes are implemented in the custom widget:\n * - with or without the value filtered\n * - switch between two values.\n *\n * @type {Connector}\n * @param {function(ToggleRenderingOptions, boolean)} renderFn Rendering function for the custom **Toggle** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomToggleWidgetParams)} Re-usable widget factory for a custom **Toggle** widget.\n * @example\n * // custom `renderFn` to render the custom ClearAll widget\n * function renderFn(ToggleRenderingOptions, isFirstRendering) {\n * ToggleRenderingOptions.widgetParams.containerNode\n * .find('a')\n * .off('click');\n *\n * var buttonHTML = `\n * \n * \n * ${ToggleRenderingOptions.value.name} (${ToggleRenderingOptions.value.count})\n * \n * `;\n *\n * ToggleRenderingOptions.widgetParams.containerNode.html(buttonHTML);\n * ToggleRenderingOptions.widgetParams.containerNode\n * .find('a')\n * .on('click', function(event) {\n * event.preventDefault();\n * event.stopPropagation();\n *\n * ToggleRenderingOptions.refine(ToggleRenderingOptions.value);\n * });\n * }\n *\n * // connect `renderFn` to Toggle logic\n * var customToggle = instantsearch.connectors.connectToggleRefinement(renderFn);\n *\n * // mount widget on the page\n * search.addWidgets([\n * customToggle({\n * containerNode: $('#custom-toggle-container'),\n * attribute: 'free_shipping',\n * })\n * ]);\n */\nexport default function connectToggleRefinement(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => {\n const { attribute, on: userOn = true, off: userOff } = widgetParams;\n\n if (!attribute) {\n throw new Error(withUsage('The `attribute` option is required.'));\n }\n\n const hasAnOffValue = userOff !== undefined;\n const hasAnOnValue = userOn !== undefined;\n const on = hasAnOnValue ? toArray(userOn).map(escapeRefinement) : undefined;\n const off = hasAnOffValue\n ? toArray(userOff).map(escapeRefinement)\n : undefined;\n\n let sendEvent;\n\n const toggleRefinementFactory = helper => ({ isRefined } = {}) => {\n // Checking\n if (!isRefined) {\n sendEvent('click', isRefined);\n if (hasAnOffValue) {\n off.forEach(v =>\n helper.removeDisjunctiveFacetRefinement(attribute, v)\n );\n }\n\n on.forEach(v => helper.addDisjunctiveFacetRefinement(attribute, v));\n } else {\n // Unchecking\n on.forEach(v => helper.removeDisjunctiveFacetRefinement(attribute, v));\n\n if (hasAnOffValue) {\n off.forEach(v => helper.addDisjunctiveFacetRefinement(attribute, v));\n }\n }\n\n helper.search();\n };\n\n const connectorState = {\n createURLFactory: (isRefined, { state, createURL }) => () => {\n const valuesToRemove = isRefined ? on : off;\n if (valuesToRemove) {\n valuesToRemove.forEach(v => {\n state.removeDisjunctiveFacetRefinement(attribute, v);\n });\n }\n\n const valuesToAdd = isRefined ? off : on;\n if (valuesToAdd) {\n valuesToAdd.forEach(v => {\n state.addDisjunctiveFacetRefinement(attribute, v);\n });\n }\n\n return createURL(state);\n },\n };\n\n return {\n $$type,\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.removeDisjunctiveFacet(attribute);\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n toggleRefinement: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState({\n state,\n helper,\n results,\n createURL,\n instantSearchInstance,\n }) {\n const isRefined = results\n ? on?.every(v => helper.state.isDisjunctiveFacetRefined(attribute, v))\n : on?.every(v => state.isDisjunctiveFacetRefined(attribute, v));\n\n let onFacetValue = {\n isRefined,\n count: 0,\n };\n\n let offFacetValue = {\n isRefined: hasAnOffValue && !isRefined,\n count: 0,\n };\n\n if (results) {\n const offValue = toArray(off || false);\n const allFacetValues = results.getFacetValues(attribute) || [];\n\n const onData = on\n ?.map(v =>\n find(allFacetValues, ({ name }) => name === unescapeRefinement(v))\n )\n .filter(v => v !== undefined);\n\n const offData = hasAnOffValue\n ? offValue\n .map(v =>\n find(\n allFacetValues,\n ({ name }) => name === unescapeRefinement(v)\n )\n )\n .filter(v => v !== undefined)\n : [];\n\n onFacetValue = {\n isRefined: onData.length ? onData.every(v => v.isRefined) : false,\n count: onData.reduce((acc, v) => acc + v.count, 0) || null,\n };\n\n offFacetValue = {\n isRefined: offData.length ? offData.every(v => v.isRefined) : false,\n count:\n offData.reduce((acc, v) => acc + v.count, 0) ||\n allFacetValues.reduce((total, { count }) => total + count, 0),\n };\n } else if (hasAnOffValue && !isRefined) {\n if (off) {\n off.forEach(v =>\n helper.addDisjunctiveFacetRefinement(attribute, v)\n );\n }\n\n helper.setPage(helper.state.page);\n }\n\n if (!sendEvent) {\n sendEvent = createSendEvent({\n instantSearchInstance,\n attribute,\n on,\n helper,\n });\n }\n const nextRefinement = isRefined ? offFacetValue : onFacetValue;\n\n return {\n value: {\n name: attribute,\n isRefined,\n count: results ? nextRefinement.count : null,\n onFacetValue,\n offFacetValue,\n },\n state,\n createURL: connectorState.createURLFactory(isRefined, {\n state,\n createURL,\n }),\n sendEvent,\n refine: toggleRefinementFactory(helper),\n widgetParams,\n };\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const isRefined =\n on &&\n on.every(v =>\n searchParameters.isDisjunctiveFacetRefined(attribute, v)\n );\n\n if (!isRefined) {\n return uiState;\n }\n\n return {\n ...uiState,\n toggle: {\n ...uiState.toggle,\n [attribute]: isRefined,\n },\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n let withFacetConfiguration = searchParameters\n .clearRefinements(attribute)\n .addDisjunctiveFacet(attribute);\n\n const isRefined = Boolean(uiState.toggle && uiState.toggle[attribute]);\n\n if (isRefined) {\n if (on) {\n on.forEach(v => {\n withFacetConfiguration = withFacetConfiguration.addDisjunctiveFacetRefinement(\n attribute,\n v\n );\n });\n }\n\n return withFacetConfiguration;\n }\n\n // It's not refined with an `off` value\n if (hasAnOffValue) {\n if (off) {\n off.forEach(v => {\n withFacetConfiguration = withFacetConfiguration.addDisjunctiveFacetRefinement(\n attribute,\n v\n );\n });\n }\n return withFacetConfiguration;\n }\n\n // It's not refined without an `off` value\n return withFacetConfiguration.setQueryParameters({\n disjunctiveFacetsRefinements: {\n ...searchParameters.disjunctiveFacetsRefinements,\n [attribute]: [],\n },\n });\n },\n };\n };\n}\n","import {\n checkRendering,\n warning,\n createDocumentationMessageGenerator,\n isEqual,\n noop,\n} from '../../lib/utils';\nimport { SearchResults } from 'algoliasearch-helper';\nimport { Connector, TransformItems, CreateURL } from '../../types';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'breadcrumb',\n connector: true,\n});\n\nexport type BreadcrumbConnectorParamsItem = {\n /**\n * Label of the category or subcategory.\n */\n label: string;\n\n /**\n * Value of breadcrumb item.\n */\n value: string;\n};\n\nexport type BreadcrumbConnectorParams = {\n /**\n * Attributes to use to generate the hierarchy of the breadcrumb.\n */\n attributes: string[];\n\n /**\n * Prefix path to use if the first level is not the root level.\n */\n rootPath?: string;\n\n /**\n * Function to transform the items passed to the templates.\n */\n transformItems?: TransformItems;\n\n /**\n * The level separator used in the records.\n *\n * @default '>'\n */\n separator?: string;\n};\n\nexport type BreadcrumbRendererOptions = {\n /**\n * Creates the URL for a single item name in the list.\n */\n createURL: CreateURL;\n\n /**\n * Array of objects defining the different values and labels.\n */\n items: BreadcrumbConnectorParamsItem[];\n\n /**\n * Sets the path of the hierarchical filter and triggers a new search.\n */\n refine: (value: BreadcrumbConnectorParamsItem['value']) => void;\n\n /**\n * True if refinement can be applied.\n */\n canRefine: boolean;\n};\n\nexport type BreadcrumbConnector = Connector<\n BreadcrumbRendererOptions,\n BreadcrumbConnectorParams\n>;\n\nconst connectBreadcrumb: BreadcrumbConnector = function connectBreadcrumb(\n renderFn,\n unmountFn = noop\n) {\n checkRendering(renderFn, withUsage());\n\n type ConnectorState = {\n refine: BreadcrumbRendererOptions['refine'];\n createURL: BreadcrumbRendererOptions['createURL'];\n };\n\n const connectorState = {} as ConnectorState;\n\n return widgetParams => {\n const {\n attributes,\n separator = ' > ',\n rootPath = null,\n transformItems = items => items,\n } = widgetParams || ({} as typeof widgetParams);\n\n if (!attributes || !Array.isArray(attributes) || attributes.length === 0) {\n throw new Error(\n withUsage('The `attributes` option expects an array of strings.')\n );\n }\n\n const [hierarchicalFacetName] = attributes;\n\n return {\n $$type: 'ais.breadcrumb',\n\n init(initOptions) {\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance: initOptions.instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance: renderOptions.instantSearchInstance,\n },\n false\n );\n },\n\n dispose() {\n unmountFn();\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n breadcrumb: {\n ...renderState.breadcrumb,\n [hierarchicalFacetName]: this.getWidgetRenderState(renderOptions),\n },\n };\n },\n\n getWidgetRenderState({ helper, createURL, results, state }) {\n function getItems() {\n if (!results) {\n return [];\n }\n\n const [{ name: facetName }] = state.hierarchicalFacets;\n\n const facetValues = results.getFacetValues(\n facetName,\n {}\n ) as SearchResults.HierarchicalFacet;\n const data = Array.isArray(facetValues.data) ? facetValues.data : [];\n const items = transformItems(shiftItemsValues(prepareItems(data)));\n\n return items;\n }\n\n const items = getItems();\n\n if (!connectorState.createURL) {\n connectorState.createURL = facetValue => {\n if (!facetValue) {\n const breadcrumb = helper.getHierarchicalFacetBreadcrumb(\n hierarchicalFacetName\n );\n if (breadcrumb.length > 0) {\n return createURL(\n helper.state.toggleFacetRefinement(\n hierarchicalFacetName,\n breadcrumb[0]\n )\n );\n }\n }\n return createURL(\n helper.state.toggleFacetRefinement(\n hierarchicalFacetName,\n facetValue\n )\n );\n };\n }\n\n if (!connectorState.refine) {\n connectorState.refine = facetValue => {\n if (!facetValue) {\n const breadcrumb = helper.getHierarchicalFacetBreadcrumb(\n hierarchicalFacetName\n );\n if (breadcrumb.length > 0) {\n helper\n .toggleRefinement(hierarchicalFacetName, breadcrumb[0])\n .search();\n }\n } else {\n helper\n .toggleRefinement(hierarchicalFacetName, facetValue)\n .search();\n }\n };\n }\n\n return {\n canRefine: items.length > 0,\n createURL: connectorState.createURL,\n items,\n refine: connectorState.refine,\n widgetParams,\n };\n },\n\n getWidgetSearchParameters(searchParameters) {\n if (searchParameters.isHierarchicalFacet(hierarchicalFacetName)) {\n const facet = searchParameters.getHierarchicalFacetByName(\n hierarchicalFacetName\n );\n\n warning(\n isEqual(facet.attributes, attributes) &&\n facet.separator === separator &&\n facet.rootPath === rootPath,\n 'Using Breadcrumb and HierarchicalMenu on the same facet with different options overrides the configuration of the HierarchicalMenu.'\n );\n\n return searchParameters;\n }\n\n return searchParameters.addHierarchicalFacet({\n name: hierarchicalFacetName,\n attributes,\n separator,\n rootPath,\n });\n },\n };\n };\n};\n\nfunction prepareItems(data) {\n return data.reduce((result, currentItem) => {\n if (currentItem.isRefined) {\n result.push({\n label: currentItem.name,\n value: currentItem.path,\n });\n if (Array.isArray(currentItem.data)) {\n result = result.concat(prepareItems(currentItem.data));\n }\n }\n return result;\n }, []);\n}\n\nfunction shiftItemsValues(array) {\n return array.map((x, idx) => ({\n label: x.label,\n value: idx + 1 === array.length ? null : array[idx + 1].value,\n }));\n}\n\nexport default connectBreadcrumb;\n","import {\n checkRendering,\n aroundLatLngToPosition,\n insideBoundingBoxToBoundingBox,\n createDocumentationMessageGenerator,\n createSendEventForHits,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'geo-search',\n connector: true,\n});\n\nconst $$type = 'ais.geoSearch';\n\n/**\n * @typedef {Object} LatLng\n * @property {number} lat The latitude in degrees.\n * @property {number} lng The longitude in degrees.\n */\n\n/**\n * @typedef {Object} Bounds\n * @property {LatLng} northEast The top right corner of the map view.\n * @property {LatLng} southWest The bottom left corner of the map view.\n */\n\n/**\n * @typedef {Object} CustomGeoSearchWidgetParams\n * @property {boolean} [enableRefineOnMapMove=true] If true, refine will be triggered as you move the map.\n * @property {function(object[]):object[]} [transformItems] Function to transform the items passed to the templates.\n */\n\n/**\n * @typedef {Object} GeoSearchRenderingOptions\n * @property {Object[]} items The matched hits from Algolia API.\n * @property {LatLng} position The current position of the search.\n * @property {Bounds} currentRefinement The current bounding box of the search.\n * @property {function(Bounds)} refine Sets a bounding box to filter the results from the given map bounds.\n * @property {function()} clearMapRefinement Reset the current bounding box refinement.\n * @property {function(): boolean} isRefinedWithMap Return true if the current refinement is set with the map bounds.\n * @property {function()} toggleRefineOnMapMove Toggle the fact that the user is able to refine on map move.\n * @property {function(): boolean} isRefineOnMapMove Return true if the user is able to refine on map move.\n * @property {function()} setMapMoveSinceLastRefine Set the fact that the map has moved since the last refinement, should be call on each map move. The call to the function triggers a new rendering only when the value change.\n * @property {function(): boolean} hasMapMoveSinceLastRefine Return true if the map has move since the last refinement.\n * @property {Object} widgetParams All original `CustomGeoSearchWidgetParams` forwarded to the `renderFn`.\n * @property {LatLng} [position] The current position of the search.\n */\n\n/**\n * The **GeoSearch** connector provides the logic to build a widget that will display the results on a map. It also provides a way to search for results based on their position. The connector provides functions to manage the search experience (search on map interaction or control the interaction for example).\n *\n * @requirements\n *\n * Note that the GeoSearch connector uses the [geosearch](https://www.algolia.com/doc/guides/searching/geo-search) capabilities of Algolia. Your hits **must** have a `_geoloc` attribute in order to be passed to the rendering function.\n *\n * Currently, the feature is not compatible with multiple values in the _geoloc attribute.\n *\n * @param {function(GeoSearchRenderingOptions, boolean)} renderFn Rendering function for the custom **GeoSearch** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomGeoSearchWidgetParams)} Re-usable widget factory for a custom **GeoSearch** widget.\n * @staticExample\n * // This example use Leaflet for the rendering, be sure to have the library correctly setup\n * // before trying the demo. You can find more details in their documentation (link below).\n * // We choose Leaflet for the example but you can use any libraries that you want.\n * // See: http://leafletjs.com/examples/quick-start\n *\n * let map = null;\n * let markers = [];\n *\n * // custom `renderFn` to render the custom GeoSearch widget\n * function renderFn(GeoSearchRenderingOptions, isFirstRendering) {\n * const { items, widgetParams } = GeoSearchRenderingOptions;\n *\n * if (isFirstRendering) {\n * map = L.map(widgetParams.container);\n *\n * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n * attribution:\n * '© OpenStreetMap contributors',\n * }).addTo(map);\n * }\n *\n * markers.forEach(marker => marker.remove());\n *\n * markers = items.map(({ _geoloc }) =>\n * L.marker([_geoloc.lat, _geoloc.lng]).addTo(map)\n * );\n *\n * if (markers.length) {\n * map.fitBounds(L.featureGroup(markers).getBounds());\n * }\n * }\n *\n * // connect `renderFn` to GeoSearch logic\n * const customGeoSearch = instantsearch.connectors.connectGeoSearch(renderFn);\n *\n * // mount widget on the page\n * search.addWidgets([\n * customGeoSearch({\n * container: document.getElementById('custom-geo-search'),\n * })\n * ]);\n */\nconst connectGeoSearch = (renderFn, unmountFn = noop) => {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => {\n const {\n enableRefineOnMapMove = true,\n transformItems = items => items,\n } = widgetParams;\n\n const widgetState = {\n isRefineOnMapMove: enableRefineOnMapMove,\n // @MAJOR hasMapMoveSinceLastRefine -> hasMapMovedSinceLastRefine\n hasMapMoveSinceLastRefine: false,\n lastRefinePosition: '',\n lastRefineBoundingBox: '',\n internalToggleRefineOnMapMove: noop,\n internalSetMapMoveSinceLastRefine: noop,\n };\n\n const getPositionFromState = state =>\n state.aroundLatLng && aroundLatLngToPosition(state.aroundLatLng);\n\n const getCurrentRefinementFromState = state =>\n state.insideBoundingBox &&\n insideBoundingBoxToBoundingBox(state.insideBoundingBox);\n\n const refine = helper => ({ northEast: ne, southWest: sw }) => {\n const boundingBox = [ne.lat, ne.lng, sw.lat, sw.lng].join();\n\n helper.setQueryParameter('insideBoundingBox', boundingBox).search();\n\n widgetState.hasMapMoveSinceLastRefine = false;\n widgetState.lastRefineBoundingBox = boundingBox;\n };\n\n const clearMapRefinement = helper => () => {\n helper.setQueryParameter('insideBoundingBox', undefined).search();\n };\n\n const isRefinedWithMap = state => () => Boolean(state.insideBoundingBox);\n\n const toggleRefineOnMapMove = () =>\n widgetState.internalToggleRefineOnMapMove();\n const createInternalToggleRefinementOnMapMove = (render, args) => () => {\n widgetState.isRefineOnMapMove = !widgetState.isRefineOnMapMove;\n\n render(args);\n };\n\n const isRefineOnMapMove = () => widgetState.isRefineOnMapMove;\n\n const setMapMoveSinceLastRefine = () =>\n widgetState.internalSetMapMoveSinceLastRefine();\n const createInternalSetMapMoveSinceLastRefine = (render, args) => () => {\n const shouldTriggerRender =\n widgetState.hasMapMoveSinceLastRefine !== true;\n\n widgetState.hasMapMoveSinceLastRefine = true;\n\n if (shouldTriggerRender) {\n render(args);\n }\n };\n\n const hasMapMoveSinceLastRefine = () =>\n widgetState.hasMapMoveSinceLastRefine;\n\n let sendEvent;\n\n return {\n $$type,\n\n init(initArgs) {\n const { instantSearchInstance } = initArgs;\n const isFirstRendering = true;\n\n widgetState.internalToggleRefineOnMapMove = createInternalToggleRefinementOnMapMove(\n noop,\n initArgs\n );\n\n widgetState.internalSetMapMoveSinceLastRefine = createInternalSetMapMoveSinceLastRefine(\n noop,\n initArgs\n );\n\n renderFn(\n {\n ...this.getWidgetRenderState(initArgs),\n instantSearchInstance,\n },\n isFirstRendering\n );\n },\n\n render(renderArgs) {\n const { helper, instantSearchInstance } = renderArgs;\n const isFirstRendering = false;\n // We don't use the state provided by the render function because we need\n // to be sure that the state is the latest one for the following condition\n const state = helper.state;\n\n const positionChangedSinceLastRefine =\n Boolean(state.aroundLatLng) &&\n Boolean(widgetState.lastRefinePosition) &&\n state.aroundLatLng !== widgetState.lastRefinePosition;\n\n const boundingBoxChangedSinceLastRefine =\n !state.insideBoundingBox &&\n Boolean(widgetState.lastRefineBoundingBox) &&\n state.insideBoundingBox !== widgetState.lastRefineBoundingBox;\n\n if (\n positionChangedSinceLastRefine ||\n boundingBoxChangedSinceLastRefine\n ) {\n widgetState.hasMapMoveSinceLastRefine = false;\n }\n\n widgetState.lastRefinePosition = state.aroundLatLng || '';\n widgetState.lastRefineBoundingBox = state.insideBoundingBox || '';\n\n widgetState.internalToggleRefineOnMapMove = createInternalToggleRefinementOnMapMove(\n this.render.bind(this),\n renderArgs\n );\n\n widgetState.internalSetMapMoveSinceLastRefine = createInternalSetMapMoveSinceLastRefine(\n this.render.bind(this),\n renderArgs\n );\n\n const widgetRenderState = this.getWidgetRenderState(renderArgs);\n\n sendEvent('view', widgetRenderState.items);\n\n renderFn(\n {\n ...widgetRenderState,\n instantSearchInstance,\n },\n isFirstRendering\n );\n },\n\n getWidgetRenderState(renderOptions) {\n const { helper, results, instantSearchInstance } = renderOptions;\n const state = helper.state;\n\n const items = results\n ? transformItems(results.hits.filter(hit => hit._geoloc))\n : [];\n\n if (!sendEvent) {\n sendEvent = createSendEventForHits({\n instantSearchInstance,\n index: helper.getIndex(),\n widgetType: $$type,\n });\n }\n\n return {\n items,\n position: getPositionFromState(state),\n currentRefinement: getCurrentRefinementFromState(state),\n refine: refine(helper),\n sendEvent,\n clearMapRefinement: clearMapRefinement(helper),\n isRefinedWithMap: isRefinedWithMap(state),\n toggleRefineOnMapMove,\n isRefineOnMapMove,\n setMapMoveSinceLastRefine,\n hasMapMoveSinceLastRefine,\n widgetParams,\n };\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n geoSearch: this.getWidgetRenderState(renderOptions),\n };\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.setQueryParameter('insideBoundingBox', undefined);\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const boundingBox = searchParameters.insideBoundingBox;\n\n if (\n !boundingBox ||\n (uiState &&\n uiState.geoSearch &&\n uiState.geoSearch.boundingBox === boundingBox)\n ) {\n return uiState;\n }\n\n return {\n ...uiState,\n geoSearch: {\n boundingBox,\n },\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n if (!uiState || !uiState.geoSearch) {\n return searchParameters.setQueryParameter(\n 'insideBoundingBox',\n undefined\n );\n }\n\n return searchParameters.setQueryParameter(\n 'insideBoundingBox',\n uiState.geoSearch.boundingBox\n );\n },\n };\n };\n};\n\nexport default connectGeoSearch;\n","import {\n checkRendering,\n createDocumentationMessageGenerator,\n noop,\n} from '../../lib/utils';\nimport { Connector } from '../../types';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'powered-by',\n connector: true,\n});\n\nexport type PoweredByRendererOptions = {\n /** the url to redirect to on click */\n url: string;\n};\n\nexport type PoweredByConnectorParams = {\n /** the url to redirect to on click */\n url?: string;\n};\n\nexport type PoweredByConnector = Connector<\n PoweredByRendererOptions,\n PoweredByConnectorParams\n>;\n\n/**\n * **PoweredBy** connector provides the logic to build a custom widget that will displays\n * the logo to redirect to Algolia.\n */\nconst connectPoweredBy: PoweredByConnector = function connectPoweredBy(\n renderFn,\n unmountFn = noop\n) {\n checkRendering(renderFn, withUsage());\n\n const defaultUrl =\n 'https://www.algolia.com/?' +\n 'utm_source=instantsearch.js&' +\n 'utm_medium=website&' +\n `utm_content=${\n typeof window !== 'undefined' && window.location\n ? window.location.hostname\n : ''\n }&` +\n 'utm_campaign=poweredby';\n\n return widgetParams => {\n const { url = defaultUrl } = widgetParams || ({} as typeof widgetParams);\n\n return {\n $$type: 'ais.poweredBy',\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n poweredBy: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState() {\n return {\n url,\n widgetParams,\n };\n },\n\n dispose() {\n unmountFn();\n },\n };\n };\n};\n\nexport default connectPoweredBy;\n","import algoliasearchHelper, {\n SearchParameters,\n PlainSearchParameters,\n AlgoliaSearchHelper,\n} from 'algoliasearch-helper';\nimport { Connector } from '../../types';\nimport {\n createDocumentationMessageGenerator,\n isPlainObject,\n mergeSearchParameters,\n noop,\n} from '../../lib/utils';\n\n/**\n * Refine the given search parameters.\n */\ntype Refine = (searchParameters: PlainSearchParameters) => void;\n\nexport type ConfigureConnectorParams = {\n /**\n * A list of [search parameters](https://www.algolia.com/doc/api-reference/search-api-parameters/)\n * to enable when the widget mounts.\n */\n searchParameters: PlainSearchParameters;\n};\n\nexport type ConfigureRendererOptions = {\n /**\n * Refine the given search parameters.\n */\n refine: Refine;\n};\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'configure',\n connector: true,\n});\n\nfunction getInitialSearchParameters(\n state: SearchParameters,\n widgetParams: ConfigureConnectorParams\n): SearchParameters {\n // We leverage the helper internals to remove the `widgetParams` from\n // the state. The function `setQueryParameters` omits the values that\n // are `undefined` on the next state.\n return state.setQueryParameters(\n Object.keys(widgetParams.searchParameters).reduce(\n (acc, key) => ({\n ...acc,\n [key]: undefined,\n }),\n {}\n )\n );\n}\n\nexport type ConfigureConnector = Connector<\n ConfigureRendererOptions,\n ConfigureConnectorParams\n>;\n\nconst connectConfigure: ConfigureConnector = function connectConfigure(\n renderFn = noop,\n unmountFn = noop\n) {\n return widgetParams => {\n if (!widgetParams || !isPlainObject(widgetParams.searchParameters)) {\n throw new Error(\n withUsage('The `searchParameters` option expects an object.')\n );\n }\n\n type ConnectorState = {\n refine?: Refine;\n };\n\n const connectorState: ConnectorState = {};\n\n function refine(helper: AlgoliaSearchHelper): Refine {\n return (searchParameters: PlainSearchParameters) => {\n // Merge new `searchParameters` with the ones set from other widgets\n const actualState = getInitialSearchParameters(\n helper.state,\n widgetParams\n );\n const nextSearchParameters = mergeSearchParameters(\n actualState,\n new algoliasearchHelper.SearchParameters(searchParameters)\n );\n\n // Update original `widgetParams.searchParameters` to the new refined one\n widgetParams.searchParameters = searchParameters;\n\n // Trigger a search with the resolved search parameters\n helper.setState(nextSearchParameters).search();\n };\n }\n\n return {\n $$type: 'ais.configure',\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n dispose({ state }) {\n unmountFn();\n\n return getInitialSearchParameters(state, widgetParams);\n },\n\n getRenderState(renderState, renderOptions) {\n const widgetRenderState = this.getWidgetRenderState(renderOptions);\n return {\n ...renderState,\n configure: {\n ...widgetRenderState,\n widgetParams: {\n ...widgetRenderState.widgetParams,\n searchParameters: mergeSearchParameters(\n new algoliasearchHelper.SearchParameters(\n renderState.configure?.widgetParams.searchParameters\n ),\n new algoliasearchHelper.SearchParameters(\n widgetRenderState.widgetParams.searchParameters\n )\n ).getQueryParams(),\n },\n },\n };\n },\n\n getWidgetRenderState({ helper }) {\n if (!connectorState.refine) {\n connectorState.refine = refine(helper);\n }\n\n return {\n refine: connectorState.refine,\n widgetParams,\n };\n },\n\n getWidgetSearchParameters(state, { uiState }) {\n return mergeSearchParameters(\n state,\n new algoliasearchHelper.SearchParameters({\n ...uiState.configure,\n ...widgetParams.searchParameters,\n })\n );\n },\n\n getWidgetUiState(uiState) {\n return {\n ...uiState,\n configure: {\n ...uiState.configure,\n ...widgetParams.searchParameters,\n },\n };\n },\n };\n };\n};\n\nexport default connectConfigure;\n","import algoliasearchHelper, {\n SearchParameters,\n PlainSearchParameters,\n} from 'algoliasearch-helper';\nimport { AlgoliaHit, Connector } from '../../types';\nimport {\n createDocumentationMessageGenerator,\n getObjectType,\n warning,\n getPropertyByPath,\n} from '../../lib/utils';\nimport connectConfigure, {\n ConfigureRendererOptions,\n} from '../configure/connectConfigure';\n\nexport type MatchingPatterns = {\n [attribute: string]: {\n /**\n * The score of the optional filter.\n *\n * @see https://www.algolia.com/doc/guides/managing-results/rules/merchandising-and-promoting/in-depth/optional-filters/\n */\n score: number;\n };\n};\n\nexport type ConfigureRelatedItemsConnectorParams = {\n /**\n * The reference hit to extract the filters from.\n */\n hit: AlgoliaHit;\n /**\n * The schema to create the optional filters.\n * Each key represents an attribute from the hit.\n */\n matchingPatterns: MatchingPatterns;\n /**\n * Function to transform the generated search parameters.\n */\n transformSearchParameters?(\n searchParameters: SearchParameters\n ): PlainSearchParameters;\n};\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'configure-related-items',\n connector: true,\n});\n\nfunction createOptionalFilter({\n attributeName,\n attributeValue,\n attributeScore,\n}) {\n return `${attributeName}:${attributeValue}`;\n}\n\nexport type ConfigureRelatedItemsConnector = Connector<\n ConfigureRendererOptions,\n ConfigureRelatedItemsConnectorParams\n>;\n\nconst connectConfigureRelatedItems: ConfigureRelatedItemsConnector = function connectConfigureRelatedItems(\n renderFn,\n unmountFn\n) {\n return widgetParams => {\n const { hit, matchingPatterns, transformSearchParameters = x => x } =\n widgetParams || ({} as typeof widgetParams);\n\n if (!hit) {\n throw new Error(withUsage('The `hit` option is required.'));\n }\n\n if (!matchingPatterns) {\n throw new Error(withUsage('The `matchingPatterns` option is required.'));\n }\n\n const optionalFilters = Object.keys(matchingPatterns).reduce<\n Array\n >((acc, attributeName) => {\n const attribute = matchingPatterns[attributeName];\n const attributeValue = getPropertyByPath(hit, attributeName);\n const attributeScore = attribute.score;\n\n if (Array.isArray(attributeValue)) {\n return [\n ...acc,\n attributeValue.map(attributeSubValue => {\n return createOptionalFilter({\n attributeName,\n attributeValue: attributeSubValue,\n attributeScore,\n });\n }),\n ];\n }\n\n if (typeof attributeValue === 'string') {\n return [\n ...acc,\n createOptionalFilter({\n attributeName,\n attributeValue,\n attributeScore,\n }),\n ];\n }\n\n warning(\n false,\n `\nThe \\`matchingPatterns\\` option returned a value of type ${getObjectType(\n attributeValue\n )} for the \"${attributeName}\" key. This value was not sent to Algolia because \\`optionalFilters\\` only supports strings and array of strings.\n\nYou can remove the \"${attributeName}\" key from the \\`matchingPatterns\\` option.\n\nSee https://www.algolia.com/doc/api-reference/api-parameters/optionalFilters/\n `\n );\n\n return acc;\n }, []);\n\n const searchParameters: PlainSearchParameters = {\n ...transformSearchParameters(\n new algoliasearchHelper.SearchParameters({\n sumOrFiltersScores: true,\n facetFilters: [`objectID:-${hit.objectID}`],\n optionalFilters,\n })\n ),\n };\n\n const makeWidget = connectConfigure(renderFn, unmountFn);\n\n return {\n // required, since widget parameters differ between these connectors\n // and we don't want to have the parameters of configure here\n ...makeWidget({ searchParameters } as any),\n $$type: 'ais.configureRelatedItems',\n };\n };\n};\n\nexport default connectConfigureRelatedItems;\n","import { SearchResults } from 'algoliasearch-helper';\nimport {\n escapeHits,\n TAG_PLACEHOLDER,\n checkRendering,\n createDocumentationMessageGenerator,\n createSendEventForHits,\n SendEventForHits,\n noop,\n warning,\n} from '../../lib/utils';\nimport { Hits, Connector } from '../../types';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'autocomplete',\n connector: true,\n});\n\nexport type AutocompleteConnectorParams = {\n /**\n * Escapes HTML entities from hits string values.\n *\n * @default `true`\n */\n escapeHTML?: boolean;\n};\n\nexport type AutocompleteRendererOptions = {\n /**\n * The current value of the query.\n */\n currentRefinement: string;\n\n /**\n * The indices this widget has access to.\n */\n indices: Array<{\n /**\n * The name of the index\n */\n indexName: string;\n\n /**\n * The resolved hits from the index matching the query.\n */\n hits: Hits;\n\n /**\n * The full results object from the Algolia API.\n */\n results: SearchResults;\n\n /**\n * Send event to insights middleware\n */\n sendEvent: SendEventForHits;\n }>;\n\n /**\n * Searches into the indices with the provided query.\n */\n refine: (query: string) => void;\n};\n\nexport type AutocompleteConnector = Connector<\n AutocompleteRendererOptions,\n AutocompleteConnectorParams\n>;\n\nconst connectAutocomplete: AutocompleteConnector = function connectAutocomplete(\n renderFn,\n unmountFn = noop\n) {\n checkRendering(renderFn, withUsage());\n\n return widgetParams => {\n const { escapeHTML = true } = widgetParams || ({} as typeof widgetParams);\n\n warning(\n !(widgetParams as any).indices,\n `\nThe option \\`indices\\` has been removed from the Autocomplete connector.\n\nThe indices to target are now inferred from the widgets tree.\n${\n Array.isArray((widgetParams as any).indices)\n ? `\nAn alternative would be:\n\nconst autocomplete = connectAutocomplete(renderer);\n\nsearch.addWidgets([\n ${(widgetParams as any).indices\n .map(({ value }: { value: string }) => `index({ indexName: '${value}' }),`)\n .join('\\n ')}\n autocomplete()\n]);\n`\n : ''\n}\n `\n );\n\n type ConnectorState = {\n refine?: (query: string) => void;\n };\n\n const connectorState: ConnectorState = {};\n\n return {\n $$type: 'ais.autocomplete',\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n const renderState = this.getWidgetRenderState(renderOptions);\n\n renderState.indices.forEach(({ sendEvent, hits }) => {\n sendEvent('view', hits);\n });\n\n renderFn(\n {\n ...renderState,\n instantSearchInstance,\n },\n false\n );\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n autocomplete: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState({ helper, scopedResults, instantSearchInstance }) {\n if (!connectorState.refine) {\n connectorState.refine = (query: string) => {\n helper.setQuery(query).search();\n };\n }\n\n const indices = scopedResults.map(scopedResult => {\n // We need to escape the hits because highlighting\n // exposes HTML tags to the end-user.\n scopedResult.results.hits = escapeHTML\n ? escapeHits(scopedResult.results.hits)\n : scopedResult.results.hits;\n\n const sendEvent = createSendEventForHits({\n instantSearchInstance,\n index: scopedResult.results.index,\n widgetType: this.$$type!,\n });\n\n return {\n indexId: scopedResult.indexId,\n indexName: scopedResult.results.index,\n hits: scopedResult.results.hits,\n results: scopedResult.results,\n sendEvent,\n };\n });\n\n return {\n currentRefinement: helper.state.query || '',\n indices,\n refine: connectorState.refine!,\n widgetParams,\n };\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const query = searchParameters.query || '';\n\n if (query === '' || (uiState && uiState.query === query)) {\n return uiState;\n }\n\n return {\n ...uiState,\n query,\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n const parameters = {\n query: uiState.query || '',\n };\n\n if (!escapeHTML) {\n return searchParameters.setQueryParameters(parameters);\n }\n\n return searchParameters.setQueryParameters({\n ...parameters,\n ...TAG_PLACEHOLDER,\n });\n },\n\n dispose({ state }) {\n unmountFn();\n\n const stateWithoutQuery = state.setQueryParameter('query', undefined);\n\n if (!escapeHTML) {\n return stateWithoutQuery;\n }\n\n return stateWithoutQuery.setQueryParameters(\n Object.keys(TAG_PLACEHOLDER).reduce(\n (acc, key) => ({\n ...acc,\n [key]: undefined,\n }),\n {}\n )\n );\n },\n };\n };\n};\n\nexport default connectAutocomplete;\n","import {\n AlgoliaSearchHelper as Helper,\n SearchParameters,\n SearchResults,\n} from 'algoliasearch-helper';\nimport { HelperChangeEvent, Connector } from '../../types';\nimport {\n checkRendering,\n createDocumentationMessageGenerator,\n warning,\n getRefinements,\n isEqual,\n noop,\n} from '../../lib/utils';\nimport {\n Refinement as InternalRefinement,\n NumericRefinement as InternalNumericRefinement,\n} from '../../lib/utils/getRefinements';\n\ntype TrackedFilterRefinement = string | number | boolean;\n\nexport type ParamTrackedFilters = {\n [facetName: string]: (\n facetValues: TrackedFilterRefinement[]\n ) => TrackedFilterRefinement[];\n};\nexport type ParamTransformRuleContexts = (ruleContexts: string[]) => string[];\ntype ParamTransformItems = (items: any[]) => any;\n\nexport type QueryRulesConnectorParams = {\n trackedFilters?: ParamTrackedFilters;\n transformRuleContexts?: ParamTransformRuleContexts;\n transformItems?: ParamTransformItems;\n};\n\nexport type QueryRulesRendererOptions = {\n items: any[];\n};\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'query-rules',\n connector: true,\n});\n\nfunction hasStateRefinements(state: SearchParameters): boolean {\n return [\n state.disjunctiveFacetsRefinements,\n state.facetsRefinements,\n state.hierarchicalFacetsRefinements,\n state.numericRefinements,\n ].some(refinement =>\n Boolean(refinement && Object.keys(refinement).length > 0)\n );\n}\n\n// A context rule must consist only of alphanumeric characters, hyphens, and underscores.\n// See https://www.algolia.com/doc/guides/managing-results/refine-results/merchandising-and-promoting/in-depth/implementing-query-rules/#context\nfunction escapeRuleContext(ruleName: string): string {\n return ruleName.replace(/[^a-z0-9-_]+/gi, '_');\n}\n\nfunction getRuleContextsFromTrackedFilters({\n helper,\n sharedHelperState,\n trackedFilters,\n}: {\n helper: Helper;\n sharedHelperState: SearchParameters;\n trackedFilters: ParamTrackedFilters;\n}): string[] {\n const ruleContexts = Object.keys(trackedFilters).reduce(\n (facets, facetName) => {\n const facetRefinements: TrackedFilterRefinement[] = getRefinements(\n // An empty object is technically not a `SearchResults` but `getRefinements`\n // only accesses properties, meaning it will not throw with an empty object.\n helper.lastResults || ({} as SearchResults),\n sharedHelperState\n )\n .filter(\n (refinement: InternalRefinement) => refinement.attribute === facetName\n )\n .map(\n (refinement: InternalRefinement) =>\n (refinement as InternalNumericRefinement).numericValue ||\n refinement.name\n );\n\n const getTrackedFacetValues = trackedFilters[facetName];\n const trackedFacetValues = getTrackedFacetValues(facetRefinements);\n\n return [\n ...facets,\n ...facetRefinements\n .filter(facetRefinement =>\n trackedFacetValues.includes(facetRefinement)\n )\n .map(facetValue =>\n escapeRuleContext(`ais-${facetName}-${facetValue}`)\n ),\n ];\n },\n []\n );\n\n return ruleContexts;\n}\n\nfunction applyRuleContexts(\n this: {\n helper: Helper;\n initialRuleContexts: string[];\n trackedFilters: ParamTrackedFilters;\n transformRuleContexts: ParamTransformRuleContexts;\n },\n event: HelperChangeEvent\n): void {\n const {\n helper,\n initialRuleContexts,\n trackedFilters,\n transformRuleContexts,\n } = this;\n\n const sharedHelperState = event.state;\n const previousRuleContexts: string[] = sharedHelperState.ruleContexts || [];\n const newRuleContexts = getRuleContextsFromTrackedFilters({\n helper,\n sharedHelperState,\n trackedFilters,\n });\n const nextRuleContexts = [...initialRuleContexts, ...newRuleContexts];\n\n warning(\n nextRuleContexts.length <= 10,\n `\nThe maximum number of \\`ruleContexts\\` is 10. They have been sliced to that limit.\nConsider using \\`transformRuleContexts\\` to minimize the number of rules sent to Algolia.\n`\n );\n\n const ruleContexts = transformRuleContexts(nextRuleContexts).slice(0, 10);\n\n if (!isEqual(previousRuleContexts, ruleContexts)) {\n helper.overrideStateWithoutTriggeringChangeEvent({\n ...sharedHelperState,\n ruleContexts,\n });\n }\n}\n\nexport type QueryRulesConnector = Connector<\n QueryRulesRendererOptions,\n QueryRulesConnectorParams\n>;\n\nconst connectQueryRules: QueryRulesConnector = function connectQueryRules(\n render,\n unmount = noop\n) {\n checkRendering(render, withUsage());\n\n return widgetParams => {\n const {\n trackedFilters = {} as ParamTrackedFilters,\n transformRuleContexts = (rules => rules) as ParamTransformRuleContexts,\n transformItems = (items => items) as ParamTransformItems,\n } = widgetParams || ({} as typeof widgetParams);\n\n Object.keys(trackedFilters).forEach(facetName => {\n if (typeof trackedFilters[facetName] !== 'function') {\n throw new Error(\n withUsage(\n `'The \"${facetName}\" filter value in the \\`trackedFilters\\` option expects a function.`\n )\n );\n }\n });\n\n const hasTrackedFilters = Object.keys(trackedFilters).length > 0;\n\n // We store the initial rule contexts applied before creating the widget\n // so that we do not override them with the rules created from `trackedFilters`.\n let initialRuleContexts: string[] = [];\n let onHelperChange: (event: HelperChangeEvent) => void;\n\n return {\n $$type: 'ais.queryRules',\n\n init(initOptions) {\n const { helper, state, instantSearchInstance } = initOptions;\n\n initialRuleContexts = state.ruleContexts || [];\n onHelperChange = applyRuleContexts.bind({\n helper,\n initialRuleContexts,\n trackedFilters,\n transformRuleContexts,\n });\n\n if (hasTrackedFilters) {\n // We need to apply the `ruleContexts` based on the `trackedFilters`\n // before the helper changes state in some cases:\n // - Some filters are applied on the first load (e.g. using `configure`)\n // - The `transformRuleContexts` option sets initial `ruleContexts`.\n if (\n hasStateRefinements(state) ||\n Boolean(widgetParams.transformRuleContexts)\n ) {\n onHelperChange({ state });\n }\n\n // We track every change in the helper to override its state and add\n // any `ruleContexts` needed based on the `trackedFilters`.\n helper.on('change', onHelperChange);\n }\n\n render(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n\n render(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n getWidgetRenderState({ results }) {\n const { userData = [] } = results || {};\n const items = transformItems(userData);\n\n return {\n items,\n widgetParams,\n };\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n queryRules: this.getWidgetRenderState(renderOptions),\n };\n },\n\n dispose({ helper, state }) {\n unmount();\n\n if (hasTrackedFilters) {\n helper.removeListener('change', onHelperChange);\n\n return state.setQueryParameter('ruleContexts', initialRuleContexts);\n }\n\n return state;\n },\n };\n };\n};\n\nexport default connectQueryRules;\n","import { CreateVoiceSearchHelper, Status, VoiceListeningState } from './types';\n\nconst createVoiceSearchHelper: CreateVoiceSearchHelper = function createVoiceSearchHelper({\n searchAsYouSpeak,\n language,\n onQueryChange,\n onStateChange,\n}) {\n const SpeechRecognitionAPI: new () => SpeechRecognition =\n (window as any).webkitSpeechRecognition ||\n (window as any).SpeechRecognition;\n const getDefaultState = (status: Status): VoiceListeningState => ({\n status,\n transcript: '',\n isSpeechFinal: false,\n errorCode: undefined,\n });\n let state: VoiceListeningState = getDefaultState('initial');\n let recognition: SpeechRecognition | undefined;\n\n const isBrowserSupported = (): boolean => Boolean(SpeechRecognitionAPI);\n\n const isListening = (): boolean =>\n state.status === 'askingPermission' ||\n state.status === 'waiting' ||\n state.status === 'recognizing';\n\n const setState = (newState: Partial = {}): void => {\n state = { ...state, ...newState };\n onStateChange();\n };\n\n const getState = (): VoiceListeningState => state;\n\n const resetState = (status: Status = 'initial'): void => {\n setState(getDefaultState(status));\n };\n\n const onStart = (): void => {\n setState({\n status: 'waiting',\n });\n };\n\n const onError = (event: Event): void => {\n setState({ status: 'error', errorCode: (event as any).error });\n };\n\n const onResult = (event: SpeechRecognitionEvent): void => {\n setState({\n status: 'recognizing',\n transcript:\n (event.results[0] &&\n event.results[0][0] &&\n event.results[0][0].transcript) ||\n '',\n isSpeechFinal: event.results[0] && event.results[0].isFinal,\n });\n if (searchAsYouSpeak && state.transcript) {\n onQueryChange(state.transcript);\n }\n };\n\n const onEnd = (): void => {\n if (!state.errorCode && state.transcript && !searchAsYouSpeak) {\n onQueryChange(state.transcript);\n }\n if (state.status !== 'error') {\n setState({ status: 'finished' });\n }\n };\n\n const startListening = (): void => {\n recognition = new SpeechRecognitionAPI();\n if (!recognition) {\n return;\n }\n resetState('askingPermission');\n recognition.interimResults = true;\n\n if (language) {\n recognition.lang = language;\n }\n\n recognition.addEventListener('start', onStart);\n recognition.addEventListener('error', onError);\n recognition.addEventListener('result', onResult);\n recognition.addEventListener('end', onEnd);\n recognition.start();\n };\n\n const dispose = (): void => {\n if (!recognition) {\n return;\n }\n recognition.stop();\n recognition.removeEventListener('start', onStart);\n recognition.removeEventListener('error', onError);\n recognition.removeEventListener('result', onResult);\n recognition.removeEventListener('end', onEnd);\n recognition = undefined;\n };\n\n const stopListening = (): void => {\n dispose();\n // Because `dispose` removes event listeners, `end` listener is not called.\n // So we're setting the `status` as `finished` here.\n // If we don't do it, it will be still `waiting` or `recognizing`.\n resetState('finished');\n };\n\n return {\n getState,\n isBrowserSupported,\n isListening,\n startListening,\n stopListening,\n dispose,\n };\n};\n\nexport default createVoiceSearchHelper;\n","import { PlainSearchParameters } from 'algoliasearch-helper';\nimport {\n checkRendering,\n createDocumentationMessageGenerator,\n noop,\n} from '../../lib/utils';\nimport { Connector } from '../../types';\nimport builtInCreateVoiceSearchHelper from '../../lib/voiceSearchHelper';\nimport {\n CreateVoiceSearchHelper,\n VoiceListeningState,\n} from '../../lib/voiceSearchHelper/types';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'voice-search',\n connector: true,\n});\n\nexport type VoiceSearchConnectorParams = {\n searchAsYouSpeak?: boolean;\n language?: string;\n additionalQueryParameters?: (params: {\n query: string;\n }) => PlainSearchParameters | void;\n createVoiceSearchHelper?: CreateVoiceSearchHelper;\n};\n\nexport type VoiceSearchRendererOptions = {\n isBrowserSupported: boolean;\n isListening: boolean;\n toggleListening: () => void;\n voiceListeningState: VoiceListeningState;\n};\n\nexport type VoiceSearchConnector = Connector<\n VoiceSearchRendererOptions,\n VoiceSearchConnectorParams\n>;\n\nconst connectVoiceSearch: VoiceSearchConnector = function connectVoiceSearch(\n renderFn,\n unmountFn = noop\n) {\n checkRendering(renderFn, withUsage());\n\n return widgetParams => {\n const {\n searchAsYouSpeak = false,\n language,\n additionalQueryParameters,\n createVoiceSearchHelper = builtInCreateVoiceSearchHelper,\n } = widgetParams;\n\n return {\n $$type: 'ais.voiceSearch',\n\n init(initOptions) {\n const { instantSearchInstance } = initOptions;\n renderFn(\n {\n ...this.getWidgetRenderState(initOptions),\n instantSearchInstance,\n },\n true\n );\n },\n\n render(renderOptions) {\n const { instantSearchInstance } = renderOptions;\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n\n getRenderState(renderState, renderOptions) {\n return {\n ...renderState,\n voiceSearch: this.getWidgetRenderState(renderOptions),\n };\n },\n\n getWidgetRenderState(renderOptions) {\n const { helper, instantSearchInstance } = renderOptions;\n if (!(this as any)._refine) {\n (this as any)._refine = (query: string): void => {\n if (query !== helper.state.query) {\n const queryLanguages = language\n ? [language.split('-')[0]]\n : undefined;\n helper.setQueryParameter('queryLanguages', queryLanguages);\n\n if (typeof additionalQueryParameters === 'function') {\n helper.setState(\n helper.state.setQueryParameters({\n ignorePlurals: true,\n removeStopWords: true,\n // @ts-ignore (optionalWords only allows array, while string is also valid)\n optionalWords: query,\n ...additionalQueryParameters({ query }),\n })\n );\n }\n\n helper.setQuery(query).search();\n }\n };\n }\n\n if (!(this as any)._voiceSearchHelper) {\n (this as any)._voiceSearchHelper = createVoiceSearchHelper({\n searchAsYouSpeak,\n language,\n onQueryChange: query => (this as any)._refine(query),\n onStateChange: () => {\n renderFn(\n {\n ...this.getWidgetRenderState(renderOptions),\n instantSearchInstance,\n },\n false\n );\n },\n });\n }\n\n const {\n isBrowserSupported,\n isListening,\n startListening,\n stopListening,\n getState,\n } = (this as any)._voiceSearchHelper;\n\n return {\n isBrowserSupported: isBrowserSupported(),\n isListening: isListening(),\n toggleListening() {\n if (!isBrowserSupported()) {\n return;\n }\n if (isListening()) {\n stopListening();\n } else {\n startListening();\n }\n },\n voiceListeningState: getState(),\n widgetParams,\n };\n },\n\n dispose({ state }) {\n (this as any)._voiceSearchHelper.dispose();\n\n unmountFn();\n\n let newState = state;\n if (typeof additionalQueryParameters === 'function') {\n const additional = additionalQueryParameters({ query: '' });\n const toReset = additional\n ? Object.keys(additional).reduce((acc, current) => {\n acc[current] = undefined;\n return acc;\n }, {})\n : {};\n newState = state.setQueryParameters({\n // @ts-ignore (queryLanguages is not yet added to algoliasearch)\n queryLanguages: undefined,\n ignorePlurals: undefined,\n removeStopWords: undefined,\n optionalWords: undefined,\n ...toReset,\n });\n }\n\n return newState.setQueryParameter('query', undefined);\n },\n\n getWidgetUiState(uiState, { searchParameters }) {\n const query = searchParameters.query || '';\n\n if (!query) {\n return uiState;\n }\n\n return {\n ...uiState,\n query,\n };\n },\n\n getWidgetSearchParameters(searchParameters, { uiState }) {\n return searchParameters.setQueryParameter('query', uiState.query || '');\n },\n };\n };\n};\n\nexport default connectVoiceSearch;\n","/*!\n Copyright (c) 2017 Jed Watson.\n Licensed under the MIT License (MIT), see\n http://jedwatson.github.io/classnames\n*/\n/* global define */\n\n(function () {\n\t'use strict';\n\n\tvar hasOwn = {}.hasOwnProperty;\n\n\tfunction classNames () {\n\t\tvar classes = [];\n\n\t\tfor (var i = 0; i < arguments.length; i++) {\n\t\t\tvar arg = arguments[i];\n\t\t\tif (!arg) continue;\n\n\t\t\tvar argType = typeof arg;\n\n\t\t\tif (argType === 'string' || argType === 'number') {\n\t\t\t\tclasses.push(arg);\n\t\t\t} else if (Array.isArray(arg) && arg.length) {\n\t\t\t\tvar inner = classNames.apply(null, arg);\n\t\t\t\tif (inner) {\n\t\t\t\t\tclasses.push(inner);\n\t\t\t\t}\n\t\t\t} else if (argType === 'object') {\n\t\t\t\tfor (var key in arg) {\n\t\t\t\t\tif (hasOwn.call(arg, key) && arg[key]) {\n\t\t\t\t\t\tclasses.push(key);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn classes.join(' ');\n\t}\n\n\tif (typeof module !== 'undefined' && module.exports) {\n\t\tclassNames.default = classNames;\n\t\tmodule.exports = classNames;\n\t} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {\n\t\t// register as 'classnames', consistent with npm package name\n\t\tdefine('classnames', [], function () {\n\t\t\treturn classNames;\n\t\t});\n\t} else {\n\t\twindow.classNames = classNames;\n\t}\n}());\n","/** @jsx h */\n\nimport { h, Component } from 'preact';\nimport PropTypes from 'prop-types';\nimport { renderTemplate, isEqual } from '../../lib/utils';\n\nclass Template extends Component {\n shouldComponentUpdate(nextProps) {\n return (\n !isEqual(this.props.data, nextProps.data) ||\n this.props.templateKey !== nextProps.templateKey ||\n !isEqual(this.props.rootProps, nextProps.rootProps)\n );\n }\n\n render() {\n const RootTagName = this.props.rootTagName;\n const useCustomCompileOptions = this.props.useCustomCompileOptions[\n this.props.templateKey\n ];\n const compileOptions = useCustomCompileOptions\n ? this.props.templatesConfig.compileOptions\n : {};\n\n const content = renderTemplate({\n templates: this.props.templates,\n templateKey: this.props.templateKey,\n compileOptions,\n helpers: this.props.templatesConfig.helpers,\n data: this.props.data,\n bindEvent: this.props.bindEvent,\n });\n\n if (content === null) {\n // Adds a noscript to the DOM but virtual DOM is null\n // See http://facebook.github.io/react/docs/component-specs.html#render\n return null;\n }\n\n return (\n \n );\n }\n}\n\nTemplate.propTypes = {\n data: PropTypes.object,\n rootProps: PropTypes.object,\n rootTagName: PropTypes.string,\n templateKey: PropTypes.string,\n templates: PropTypes.objectOf(\n PropTypes.oneOfType([PropTypes.string, PropTypes.func])\n ),\n templatesConfig: PropTypes.shape({\n helpers: PropTypes.objectOf(PropTypes.func),\n // https://github.com/twitter/hogan.js/#compilation-options\n compileOptions: PropTypes.shape({\n asString: PropTypes.bool,\n sectionTags: PropTypes.arrayOf(\n PropTypes.shape({\n o: PropTypes.string,\n c: PropTypes.string,\n })\n ),\n delimiters: PropTypes.string,\n disableLambda: PropTypes.bool,\n }),\n }),\n useCustomCompileOptions: PropTypes.objectOf(PropTypes.bool),\n bindEvent: PropTypes.func,\n};\n\nTemplate.defaultProps = {\n data: {},\n rootTagName: 'div',\n useCustomCompileOptions: {},\n templates: {},\n templatesConfig: {},\n};\n\nexport default Template;\n","/** @jsx h */\n\nimport { h } from 'preact';\nimport PropTypes from 'prop-types';\nimport cx from 'classnames';\nimport Template from '../Template/Template';\n\nconst ClearRefinements = ({\n hasRefinements,\n refine,\n cssClasses,\n templateProps,\n}) => (\n
    \n \n
    \n);\n\nClearRefinements.propTypes = {\n refine: PropTypes.func.isRequired,\n cssClasses: PropTypes.shape({\n root: PropTypes.string.isRequired,\n button: PropTypes.string.isRequired,\n disabledButton: PropTypes.string.isRequired,\n }).isRequired,\n hasRefinements: PropTypes.bool.isRequired,\n templateProps: PropTypes.object.isRequired,\n};\n\nexport default ClearRefinements;\n","/** @jsx h */\n\nimport { h } from 'preact';\nimport { isSpecialClick, capitalize } from '../../lib/utils';\nimport {\n CurrentRefinementsConnectorParamsItem,\n CurrentRefinementsConnectorParamsRefinement,\n} from '../../connectors/current-refinements/connectCurrentRefinements';\nimport { CurrentRefinementsCSSClasses } from '../../widgets/current-refinements/current-refinements';\n\nexport type CurrentRefinementsComponentCSSClasses = {\n [TClassName in keyof CurrentRefinementsCSSClasses]: string;\n};\n\ntype CurrentRefinementsProps = {\n items: CurrentRefinementsConnectorParamsItem[];\n cssClasses: CurrentRefinementsComponentCSSClasses;\n};\n\nconst createItemKey = ({\n attribute,\n value,\n type,\n operator,\n}: CurrentRefinementsConnectorParamsRefinement): string =>\n [attribute, type, value, operator]\n .map(key => key)\n .filter(Boolean)\n .join(':');\n\nconst handleClick = (callback: () => void) => (event: any) => {\n if (isSpecialClick(event)) {\n return;\n }\n\n event.preventDefault();\n callback();\n};\n\nconst CurrentRefinements = ({ items, cssClasses }: CurrentRefinementsProps) => (\n
    \n
      \n {items.map((item, index) => (\n \n {capitalize(item.label)}:\n\n {item.refinements.map(refinement => (\n \n \n {refinement.attribute === 'query' ? (\n {refinement.label}\n ) : (\n refinement.label\n )}\n \n\n \n ✕\n \n \n ))}\n \n ))}\n
    \n
    \n);\n\nexport default CurrentRefinements;\n","function capitalize(text: string): string {\n return (\n text\n .toString()\n .charAt(0)\n .toUpperCase() + text.toString().slice(1)\n );\n}\n\nexport default capitalize;\n","/** @jsx h */\n\nimport { h, render } from 'preact';\nimport cx from 'classnames';\nimport CurrentRefinements from '../../components/CurrentRefinements/CurrentRefinements';\nimport connectCurrentRefinements, {\n CurrentRefinementsRendererOptions,\n CurrentRefinementsConnectorParams,\n} from '../../connectors/current-refinements/connectCurrentRefinements';\nimport {\n getContainerNode,\n createDocumentationMessageGenerator,\n} from '../../lib/utils';\nimport { component } from '../../lib/suit';\nimport { WidgetFactory } from '../../types';\n\nexport type CurrentRefinementsCSSClasses = {\n /**\n * CSS class to add to the root element.\n */\n root?: string | string[];\n\n /**\n * CSS class to add to the list element.\n */\n list?: string | string[];\n\n /**\n * CSS class to add to the each item element.\n */\n item?: string | string[];\n\n /**\n * CSS class to add to the label element.\n */\n label?: string | string[];\n\n /**\n * CSS class to add to the category element.\n */\n category?: string | string[];\n\n /**\n * CSS class to add to the categoryLabel element.\n */\n categoryLabel?: string | string[];\n\n /**\n * CSS class to add to the delete element.\n */\n delete?: string | string[];\n};\n\nexport type CurrentRefinementsWidgetParams = {\n /**\n * The CSS Selector or `HTMLElement` to insert the widget into.\n */\n container: string | HTMLElement;\n\n /**\n * The CSS classes to override.\n */\n cssClasses?: CurrentRefinementsCSSClasses;\n};\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'current-refinements',\n});\nconst suit = component('CurrentRefinements');\n\nconst renderer = ({ items, widgetParams }, isFirstRender) => {\n if (isFirstRender) {\n return;\n }\n\n const { container, cssClasses } = widgetParams;\n\n render(\n ,\n container\n );\n};\n\nexport type CurrentRefinementsWidget = WidgetFactory<\n CurrentRefinementsRendererOptions,\n CurrentRefinementsConnectorParams,\n CurrentRefinementsWidgetParams\n>;\n\nconst currentRefinements: CurrentRefinementsWidget = function currentRefinements(\n widgetParams\n) {\n const {\n container,\n includedAttributes,\n excludedAttributes,\n cssClasses: userCssClasses = {},\n transformItems,\n } = widgetParams || ({} as typeof widgetParams);\n\n if (!container) {\n throw new Error(withUsage('The `container` option is required.'));\n }\n\n const containerNode = getContainerNode(container);\n const cssClasses = {\n root: cx(suit(), userCssClasses.root),\n list: cx(suit({ descendantName: 'list' }), userCssClasses.list),\n item: cx(suit({ descendantName: 'item' }), userCssClasses.item),\n label: cx(suit({ descendantName: 'label' }), userCssClasses.label),\n category: cx(suit({ descendantName: 'category' }), userCssClasses.category),\n categoryLabel: cx(\n suit({ descendantName: 'categoryLabel' }),\n userCssClasses.categoryLabel\n ),\n delete: cx(suit({ descendantName: 'delete' }), userCssClasses.delete),\n };\n\n const makeWidget = connectCurrentRefinements(\n renderer,\n () => render(null, containerNode)\n );\n\n return {\n ...makeWidget({\n container: containerNode,\n cssClasses,\n includedAttributes,\n excludedAttributes,\n transformItems,\n }),\n $$widgetType: 'ais.currentRefinements',\n };\n};\n\nexport default currentRefinements;\n","/** @jsx h */\n\nimport { h } from 'preact';\nimport PropTypes from 'prop-types';\n\nconst GeoSearchButton = ({ className, disabled, onClick, children }) => (\n \n);\n\nGeoSearchButton.propTypes = {\n className: PropTypes.string.isRequired,\n onClick: PropTypes.func.isRequired,\n children: PropTypes.node.isRequired,\n disabled: PropTypes.bool,\n};\n\nGeoSearchButton.defaultProps = {\n disabled: false,\n};\n\nexport default GeoSearchButton;\n","export default {\n resetLabel: 'Clear refinements',\n};\n","/** @jsx h */\n\nimport { h, render } from 'preact';\nimport ClearRefinements from '../../components/ClearRefinements/ClearRefinements';\nimport cx from 'classnames';\nimport connectClearRefinements, {\n ClearRefinementsConnectorParams,\n ClearRefinementsRendererOptions,\n} from '../../connectors/clear-refinements/connectClearRefinements';\nimport defaultTemplates from './defaultTemplates';\nimport {\n getContainerNode,\n prepareTemplateProps,\n createDocumentationMessageGenerator,\n} from '../../lib/utils';\nimport { component } from '../../lib/suit';\nimport { WidgetFactory, Template } from '../../types';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'clear-refinements',\n});\nconst suit = component('ClearRefinements');\n\nconst renderer = ({ containerNode, cssClasses, renderState, templates }) => (\n { refine, hasRefinements, instantSearchInstance },\n isFirstRendering\n) => {\n if (isFirstRendering) {\n renderState.templateProps = prepareTemplateProps({\n defaultTemplates,\n templatesConfig: instantSearchInstance.templatesConfig,\n templates,\n });\n return;\n }\n\n render(\n ,\n containerNode\n );\n};\n\nexport type ClearRefinementsCSSClasses = {\n /**\n * CSS class to add to the wrapper element.\n */\n root?: string | string[];\n\n /**\n * CSS class to add to the button of the widget.\n */\n button?: string | string[];\n\n /**\n * CSS class to add to the button when there are no refinements.\n */\n disabledButton?: string | string[];\n};\n\nexport type ClearRefinementsTemplates = {\n /**\n * Template for the content of the button\n */\n resetLabel?: Template;\n};\n\nexport type ClearRefinementsWidgetParams = {\n /**\n * CSS Selector or HTMLElement to insert the widget.\n */\n container: string | HTMLElement;\n\n /**\n * Templates to use for the widget.\n */\n templates?: ClearRefinementsTemplates;\n\n /**\n * CSS classes to be added.\n */\n cssClasses?: ClearRefinementsCSSClasses;\n};\n\nexport type ClearRefinementsWidget = WidgetFactory<\n ClearRefinementsRendererOptions,\n ClearRefinementsConnectorParams,\n ClearRefinementsWidgetParams\n>;\n\nconst clearRefinements: ClearRefinementsWidget = widgetParams => {\n const {\n container,\n templates = defaultTemplates,\n includedAttributes,\n excludedAttributes,\n transformItems,\n cssClasses: userCssClasses = {},\n } = widgetParams || ({} as typeof widgetParams);\n\n if (!container) {\n throw new Error(withUsage('The `container` option is required.'));\n }\n\n const containerNode = getContainerNode(container);\n\n const cssClasses = {\n root: cx(suit(), userCssClasses.root),\n button: cx(suit({ descendantName: 'button' }), userCssClasses.button),\n disabledButton: cx(\n suit({ descendantName: 'button', modifierName: 'disabled' }),\n userCssClasses.disabledButton\n ),\n };\n\n const specializedRenderer = renderer({\n containerNode,\n cssClasses,\n renderState: {},\n templates,\n });\n\n const makeWidget = connectClearRefinements(specializedRenderer, () =>\n render(null, containerNode)\n );\n\n return {\n ...makeWidget({\n includedAttributes,\n excludedAttributes,\n transformItems,\n }),\n $$widgetType: 'ais.clearRefinements',\n };\n};\n\nexport default clearRefinements;\n","/** @jsx h */\n\nimport { h } from 'preact';\nimport PropTypes from 'prop-types';\n\nconst GeoSearchToggle = ({\n classNameLabel,\n classNameInput,\n checked,\n onToggle,\n children,\n}) => (\n \n);\n\nGeoSearchToggle.propTypes = {\n classNameLabel: PropTypes.string.isRequired,\n classNameInput: PropTypes.string.isRequired,\n checked: PropTypes.bool.isRequired,\n onToggle: PropTypes.func.isRequired,\n children: PropTypes.node.isRequired,\n};\n\nexport default GeoSearchToggle;\n","/** @jsx h */\n\nimport { h } from 'preact';\nimport PropTypes from 'prop-types';\nimport cx from 'classnames';\nimport Template from '../Template/Template';\nimport GeoSearchButton from './GeoSearchButton';\nimport GeoSearchToggle from './GeoSearchToggle';\n\nconst GeoSearchControls = ({\n cssClasses,\n enableRefine,\n enableRefineControl,\n enableClearMapRefinement,\n isRefineOnMapMove,\n isRefinedWithMap,\n hasMapMoveSinceLastRefine,\n onRefineToggle,\n onRefineClick,\n onClearClick,\n templateProps,\n}) =>\n enableRefine && (\n
    \n {enableRefineControl && (\n
    \n {isRefineOnMapMove || !hasMapMoveSinceLastRefine ? (\n \n \n \n ) : (\n \n \n \n )}\n
    \n )}\n\n {!enableRefineControl && !isRefineOnMapMove && (\n
    \n \n \n \n
    \n )}\n\n {enableClearMapRefinement && isRefinedWithMap && (\n \n