{"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/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/getPropertyByPath.ts","../src/lib/utils/noop.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/mergeSearchParameters.ts","../src/lib/utils/findIndex.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/widgets/index/index.ts","../src/lib/utils/resolveSearchParameters.ts","../src/lib/escape-highlight.ts","../src/lib/suit.ts","../src/helpers/highlight.ts","../src/helpers/snippet.ts","../src/helpers/insights.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/lib/InstantSearch.ts","../src/lib/version.ts","../src/lib/createHelpers.ts","../src/middleware/createRouter.ts","../src/connectors/clear-refinements/connectClearRefinements.js","../src/connectors/current-refinements/connectCurrentRefinements.ts","../src/connectors/hierarchical-menu/connectHierarchicalMenu.js","../src/connectors/hits/connectHits.js","../src/lib/insights/client.ts","../node_modules/preact/dist/preact.module.js","../src/lib/insights/listener.tsx","../src/connectors/hits/connectHitsWithInsights.ts","../src/connectors/hits-per-page/connectHitsPerPage.js","../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/Paginator.js","../src/connectors/pagination/connectPagination.js","../src/connectors/range/connectRange.js","../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.js","../src/connectors/geo-search/connectGeoSearch.js","../src/connectors/powered-by/connectPoweredBy.js","../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/react-is/cjs/react-is.production.min.js","../node_modules/react-is/index.js","../node_modules/object-assign/index.js","../node_modules/prop-types/factoryWithTypeCheckers.js","../node_modules/prop-types/factoryWithThrowingShims.js","../node_modules/prop-types/index.js","../node_modules/prop-types/lib/ReactPropTypesSecret.js","../node_modules/classnames/index.js","../src/components/Template/Template.js","../src/components/ClearRefinements/ClearRefinements.js","../src/widgets/clear-refinements/defaultTemplates.js","../src/widgets/clear-refinements/clear-refinements.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/configure/configure.ts","../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.js","../src/widgets/hits/hits.js","../src/components/Selector/Selector.js","../src/widgets/hits-per-page/hits-per-page.js","../src/components/InfiniteHits/InfiniteHits.tsx","../src/widgets/infinite-hits/defaultTemplates.ts","../src/widgets/infinite-hits/infinite-hits.tsx","../src/widgets/menu/defaultTemplates.js","../src/widgets/menu/menu.js","../src/widgets/refinement-list/defaultTemplates.js","../src/widgets/refinement-list/refinement-list.js","../src/widgets/numeric-menu/defaultTemplates.js","../src/widgets/numeric-menu/numeric-menu.js","../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/defaultTemplates.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/widgets/analytics/analytics.ts","../src/components/Breadcrumb/Breadcrumb.tsx","../src/widgets/breadcrumb/defaultTemplates.js","../src/widgets/breadcrumb/breadcrumb.js","../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-related-items/configure-related-items.ts","../src/widgets/geo-search/createHTMLMarker.js","../src/widgets/places/places.ts","../src/lib/stateMappings/singleIndex.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\nmodule.exports = function defaultsPure() {\n var sources = Array.prototype.slice.call(arguments);\n return sources.reduceRight(function(acc, source) {\n Object.keys(Object(source)).forEach(function(key) {\n if (source[key] !== undefined) {\n acc[key] = source[key];\n }\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\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');\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 /**\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 return geoRect.map(function(value) {\n return parseFloat(value);\n });\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 if (!objectHasKeys(this.numericRefinements[attribute])) {\n return this.numericRefinements;\n }\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 /**\n * query used to generate the results\n * @member {string}\n */\n this.query = mainSubResponse.query;\n /**\n * The query as parsed by the engine given all the rules.\n * @member {string}\n */\n this.parsedQuery = mainSubResponse.parsedQuery;\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 * @member {object[]}\n */\n this.hits = mainSubResponse.hits;\n /**\n * index where the results come from\n * @member {string}\n */\n this.index = mainSubResponse.index;\n /**\n * number of hits per page requested\n * @member {number}\n */\n this.hitsPerPage = mainSubResponse.hitsPerPage;\n /**\n * total number of hits of this query on the index\n * @member {number}\n */\n this.nbHits = mainSubResponse.nbHits;\n /**\n * total number of pages with respect to the number of hits per page and the total number of hits\n * @member {number}\n */\n this.nbPages = mainSubResponse.nbPages;\n /**\n * current page\n * @member {number}\n */\n this.page = mainSubResponse.page;\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 * The position if the position was guessed by IP.\n * @member {string}\n * @example \"48.8637,2.3615\",\n */\n this.aroundLatLng = mainSubResponse.aroundLatLng;\n /**\n * The radius computed by Algolia.\n * @member {string}\n * @example \"126792922\",\n */\n this.automaticRadius = mainSubResponse.automaticRadius;\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 * @member {string}\n * @example \"c7-use-2.algolia.net\",\n */\n this.serverUsed = mainSubResponse.serverUsed;\n /**\n * Boolean that indicates if the computation of the counts did time out.\n * @deprecated\n * @member {boolean}\n */\n this.timeoutCounts = mainSubResponse.timeoutCounts;\n /**\n * Boolean that indicates if the computation of the hits did time out.\n * @deprecated\n * @member {boolean}\n */\n this.timeoutHits = mainSubResponse.timeoutHits;\n\n /**\n * True if the counts of the facets is exhaustive\n * @member {boolean}\n */\n this.exhaustiveFacetsCount = mainSubResponse.exhaustiveFacetsCount;\n\n /**\n * True if the number of hits is exhaustive\n * @member {boolean}\n */\n this.exhaustiveNbHits = mainSubResponse.exhaustiveNbHits;\n\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 * @member {object[]}\n */\n this.userData = mainSubResponse.userData;\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 * @member {string}\n */\n this.queryID = mainSubResponse.queryID;\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 var self = this;\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.1.1';\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}) {\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);\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 thisArg?: any\n): TItem | undefined {\n if (!Array.prototype.find) {\n return items.filter(predicate, thisArg)[0];\n }\n\n return items.find(predicate, thisArg);\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 interface 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 interface QueryRefinement\n extends Pick {\n type: 'query';\n query: string;\n}\n\nexport interface NumericRefinement extends FacetRefinement {\n type: 'numeric';\n numericValue: number;\n operator: '<' | '<=' | '=' | '!=' | '>=' | '>';\n}\n\nexport interface FacetExcludeRefinement extends FacetRefinement {\n type: 'exclude';\n exclude: boolean;\n}\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(rendering: Renderer, usage: string): 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 getPropertyByPath(object: object, path: string): any {\n const parts = path.split('.');\n\n return parts.reduce((current, key) => current && current[key], object);\n}\n\nexport default getPropertyByPath;\n","function noop(..._args: any[]): void {}\n\nexport default noop;\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","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","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, {\n AlgoliaSearchHelper as Helper,\n DerivedHelper,\n PlainSearchParameters,\n SearchParameters,\n SearchResults,\n} from 'algoliasearch-helper';\nimport {\n InstantSearch,\n UiState,\n IndexUiState,\n Widget,\n InitOptions,\n RenderOptions,\n WidgetStateOptions,\n WidgetSearchParametersOptions,\n ScopedResult,\n Client,\n} from '../../types';\nimport {\n createDocumentationMessageGenerator,\n resolveSearchParameters,\n mergeSearchParameters,\n warning,\n capitalize,\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 getParent(): Index | null;\n getWidgets(): Widget[];\n addWidgets(widgets: Widget[]): Index;\n removeWidgets(widgets: Widget[]): Index;\n init(options: IndexInitOptions): void;\n render(options: IndexRenderOptions): void;\n dispose(): void;\n getWidgetState(uiState: UiState): UiState;\n};\n\nfunction isIndexWidget(widget: Widget): widget is Index {\n return widget.$$type === 'ais.index';\n}\n\nfunction getLocalWidgetsState(\n widgets: Widget[],\n widgetStateOptions: WidgetStateOptions\n): IndexUiState {\n return widgets\n .filter(widget => !isIndexWidget(widget))\n .reduce((uiState, widget) => {\n if (!widget.getWidgetState) {\n return uiState;\n }\n\n return widget.getWidgetState(uiState, widgetStateOptions);\n }, {});\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 // @ts-ignore @TODO: remove \"ts-ignore\" once `resetPage()` is typed in the helper\n widgetHelper.setState(widgetHelper.state.resetPage());\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\nfunction resolveScopedResultsFromIndex(widget: Index): ScopedResult[] {\n const widgetParent = widget.getParent();\n // If the widget is the root, we consider itself as the only sibling.\n const widgetSiblings = widgetParent ? widgetParent.getWidgets() : [widget];\n\n return resolveScopedResultsFromWidgets(widgetSiblings);\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 const createURL = (nextState: SearchParameters) =>\n localInstantSearchInstance!._createURL!({\n [indexId]: getLocalWidgetsState(localWidgets, {\n searchParameters: nextState,\n helper: helper!,\n }),\n });\n\n return {\n $$type: '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 getParent() {\n return localParent;\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 helper!.setState(\n getLocalWidgetsSearchParameters(localWidgets, {\n uiState: localUiState,\n initialSearchParameters: helper!.state,\n })\n );\n\n widgets.forEach(widget => {\n if (localInstantSearchInstance && widget.init) {\n widget.init({\n helper: helper!,\n parent: this,\n uiState: {},\n instantSearchInstance: localInstantSearchInstance,\n state: helper!.state,\n templatesConfig: localInstantSearchInstance.templatesConfig,\n createURL,\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 = getLocalWidgetsState(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 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({} as Client, parameters.index, parameters);\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 = () => mainHelper.search();\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 // Some connectors are responsible for multiple widgets so we need\n // to map them.\n // eslint-disable-next-line no-inner-declarations\n function getWidgetNames(connectorName: string): string[] {\n switch (connectorName) {\n case 'range':\n return ['rangeInput', 'rangeSlider'];\n\n case 'menu':\n return ['menu', 'menuSelect'];\n\n default:\n return [connectorName];\n }\n }\n\n type StateDescription = {\n connectors: string[];\n widgets: Array;\n };\n\n type StateToWidgets = {\n [TParameter in keyof IndexUiState]: StateDescription;\n };\n\n const stateToWidgetsMap: StateToWidgets = {\n query: {\n connectors: ['connectSearchBox'],\n widgets: ['ais.searchBox', 'ais.autocomplete', 'ais.voiceSearch'],\n },\n refinementList: {\n connectors: ['connectRefinementList'],\n widgets: ['ais.refinementList'],\n },\n menu: {\n connectors: ['connectMenu'],\n widgets: ['ais.menu'],\n },\n hierarchicalMenu: {\n connectors: ['connectHierarchicalMenu'],\n widgets: ['ais.hierarchicalMenu'],\n },\n numericMenu: {\n connectors: ['connectNumericMenu'],\n widgets: ['ais.numericMenu'],\n },\n ratingMenu: {\n connectors: ['connectRatingMenu'],\n widgets: ['ais.ratingMenu'],\n },\n range: {\n connectors: ['connectRange'],\n widgets: ['ais.rangeInput', 'ais.rangeSlider'],\n },\n toggle: {\n connectors: ['connectToggleRefinement'],\n widgets: ['ais.toggleRefinement'],\n },\n geoSearch: {\n connectors: ['connectGeoSearch'],\n widgets: ['ais.geoSearch'],\n },\n sortBy: {\n connectors: ['connectSortBy'],\n widgets: ['ais.sortBy'],\n },\n page: {\n connectors: ['connectPagination'],\n widgets: ['ais.pagination', 'ais.infiniteHits'],\n },\n hitsPerPage: {\n connectors: ['connectHitsPerPage'],\n widgets: ['ais.hitsPerPage'],\n },\n configure: {\n connectors: ['connectConfigure'],\n widgets: ['ais.configure'],\n },\n places: {\n connectors: [],\n widgets: ['ais.places'],\n },\n };\n\n const mountedWidgets = this.getWidgets()\n .map(widget => widget.$$type)\n .filter(Boolean);\n\n type MissingWidgets = Array<[string, StateDescription]>;\n\n const missingWidgets = Object.keys(localUiState).reduce<\n MissingWidgets\n >((acc, parameter) => {\n const requiredWidgets: Array | undefined =\n stateToWidgetsMap[parameter] &&\n stateToWidgetsMap[parameter].widgets;\n\n if (\n requiredWidgets &&\n !requiredWidgets.some(requiredWidget =>\n mountedWidgets.includes(requiredWidget)\n )\n ) {\n acc.push([\n parameter,\n {\n connectors: stateToWidgetsMap[parameter].connectors,\n widgets: stateToWidgetsMap[parameter].widgets.map(\n (widgetIdentifier: string) =>\n widgetIdentifier.split('ais.')[1]\n ),\n },\n ]);\n }\n\n return acc;\n }, []);\n\n warning(\n missingWidgets.length === 0,\n `The UI state for the index \"${this.getIndexId()}\" is not consistent with the widgets mounted.\n\nThis can happen when the UI state is specified via \\`initialUiState\\` or \\`routing\\` but that the widgets responsible for this state were not added. This results in those query parameters not being sent to the API.\n\nTo fully reflect the state, some widgets need to be added to the index \"${this.getIndexId()}\":\n\n${missingWidgets\n .map(([stateParameter, { widgets }]) => {\n return `- \\`${stateParameter}\\` needs one of these widgets: ${([] as string[])\n .concat(...widgets.map(name => getWidgetNames(name!)))\n .map((name: string) => `\"${name}\"`)\n .join(', ')}`;\n })\n .join('\\n')}\n\nIf you do not wish to display widgets but still want to support their search parameters, you can mount \"virtual widgets\" that don't render anything:\n\n\\`\\`\\`\n${missingWidgets\n .filter(([_stateParameter, { connectors }]) => {\n return connectors.length > 0;\n })\n .map(([_stateParameter, { connectors, widgets }]) => {\n const capitalizedWidget = capitalize(widgets[0]!);\n const connectorName = connectors[0];\n\n return `const virtual${capitalizedWidget} = ${connectorName}(() => null);`;\n })\n .join('\\n')}\n\nsearch.addWidgets([\n ${missingWidgets\n .filter(([_stateParameter, { connectors }]) => {\n return connectors.length > 0;\n })\n .map(([_stateParameter, { widgets }]) => {\n const capitalizedWidget = capitalize(widgets[0]!);\n\n return `virtual${capitalizedWidget}({ /* ... */ })`;\n })\n .join(',\\n ')}\n]);\n\\`\\`\\`\n\nIf you're using custom widgets that do set these query parameters, we recommend using connectors instead.\n\nSee https://www.algolia.com/doc/guides/building-search-ui/widgets/customize-an-existing-widget/js/#customize-the-complete-ui-of-the-widgets`\n );\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 localWidgets.forEach(widget => {\n if (widget.init) {\n widget.init({\n uiState,\n helper: helper!,\n parent: this,\n instantSearchInstance,\n state: helper!.state,\n templatesConfig: instantSearchInstance.templatesConfig,\n createURL,\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', ({ state }) => {\n localUiState = getLocalWidgetsState(localWidgets, {\n searchParameters: state,\n helper: helper!,\n });\n\n instantSearchInstance.onStateChange();\n });\n },\n\n render({ instantSearchInstance }: IndexRenderOptions) {\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 && derivedHelper!.lastResults) {\n widget.render({\n helper: helper!,\n instantSearchInstance,\n results: derivedHelper!.lastResults,\n scopedResults: resolveScopedResultsFromIndex(this),\n state: derivedHelper!.lastResults._state,\n templatesConfig: instantSearchInstance.templatesConfig,\n 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 getWidgetState(uiState: UiState) {\n return localWidgets\n .filter(isIndexWidget)\n .reduce(\n (previousUiState, innerIndex) =>\n innerIndex.getWidgetState(previousUiState),\n {\n ...uiState,\n [this.getIndexId()]: localUiState,\n }\n );\n },\n };\n};\n\nexport default index;\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","import { Hit, FacetHit } from '../types';\nimport { isPlainObject, escape } from '../lib/utils';\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 default function escapeHits(hits: Hit[]): Hit[] {\n if ((hits as any).__escaped === undefined) {\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;\n}\n\nexport function escapeFacets(facetHits: FacetHit[]): FacetHit[] {\n return facetHits.map(h => ({\n ...h,\n highlighted: replaceTagsAndEscape(h.highlighted),\n }));\n}\n","const NAMESPACE = 'ais';\n\ninterface 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 { getPropertyByPath } from '../lib/utils';\nimport { TAG_REPLACEMENT } from '../lib/escape-highlight';\nimport { component } from '../lib/suit';\n\nexport type HighlightOptions = {\n attribute: string;\n highlightedTagName?: string;\n hit: Partial;\n cssClasses?: {\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 attributeValue =\n (getPropertyByPath(hit, `_highlightResult.${attribute}.value`) as string) ||\n '';\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 { getPropertyByPath } from '../lib/utils';\nimport { TAG_REPLACEMENT } from '../lib/escape-highlight';\nimport { component } from '../lib/suit';\n\nexport type SnippetOptions = {\n attribute: 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 attributeValue =\n (getPropertyByPath(hit, `_snippetResult.${attribute}.value`) as string) ||\n '';\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 { InsightsClientMethod, InsightsClientPayload } from '../types';\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\nexport default function insights(\n method: InsightsClientMethod,\n payload: Partial\n): string {\n return writeDataAttributes({ method, payload });\n}\n","import { UiState, IndexUiState, StateMapping } 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 BrowserHistoryProps = {\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?: BrowserHistoryProps['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: BrowserHistoryProps['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: BrowserHistoryProps['createURL'];\n /**\n * Parses the URL into a route state.\n * It should be symetrical to `createURL`.\n */\n private readonly parseURL: BrowserHistoryProps['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 }: BrowserHistoryProps = {} as BrowserHistoryProps\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\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(...args: BrowserHistoryProps[]): BrowserHistory {\n return new BrowserHistory(...args);\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 algoliasearchHelper, { AlgoliaSearchHelper } from 'algoliasearch-helper';\nimport EventEmitter from 'events';\nimport index, { Index } from '../widgets/index/index';\nimport version from './version';\nimport createHelpers from './createHelpers';\nimport {\n createDocumentationMessageGenerator,\n createDocumentationLink,\n defer,\n noop,\n warning,\n} from './utils';\nimport {\n InsightsClient as AlgoliaInsightsClient,\n SearchClient,\n Widget,\n UiState,\n Client as AlgoliaSearchClient,\n} from '../types';\nimport hasDetectedInsightsClient from './utils/detect-insights-client';\nimport { Middleware, MiddlewareDefinition } from '../middleware';\nimport { createRouter, RouterProps } from '../middleware/createRouter';\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 | AlgoliaSearchClient;\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 * 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 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 helper: AlgoliaSearchHelper | null;\n public mainHelper: AlgoliaSearchHelper | null;\n public mainIndex: Index;\n public started: boolean;\n public templatesConfig: object;\n public _stalledSearchDelay: number;\n public _searchStalledTimer: any;\n public _isSearchStalled: boolean;\n public _initialUiState: UiState;\n public _createURL: (nextState: UiState) => string;\n public _searchFunction?: InstantSearchOptions['searchFunction'];\n public _mainHelperSearch?: AlgoliaSearchHelper['search'];\n public middleware: MiddlewareDefinition[] = [];\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 } = 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 (\n typeof (searchClient as AlgoliaSearchClient).addAlgoliaAgent ===\n 'function'\n ) {\n (searchClient as AlgoliaSearchClient).addAlgoliaAgent(\n `instantsearch.js (${version})`\n );\n }\n\n warning(\n Boolean(insightsClient) || !hasDetectedInsightsClient(),\n withUsage(`InstantSearch detected the Insights client in the global scope.\nTo connect InstantSearch to the Insights client, make sure to specify the \\`insightsClient\\` option:\n\nconst search = instantsearch({\n /* ... */\n insightsClient: window.aa,\n});`)\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\n this.indexName = indexName;\n this.helper = null;\n this.mainHelper = null;\n this.mainIndex = index({\n indexName,\n });\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 if (routing) {\n const routerOptions = typeof routing === 'boolean' ? undefined : routing;\n this.EXPERIMENTAL_use(createRouter(routerOptions));\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 EXPERIMENTAL_use(...middleware: Middleware[]): this {\n const newMiddlewareList = middleware.map(fn => {\n const newMiddleware = fn({ instantSearchInstance: this });\n this.middleware.push(newMiddleware);\n\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 /**\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 AlgoliaSearchClient;\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.middleware.forEach(m => {\n m.subscribe();\n });\n\n this.mainIndex.init({\n instantSearchInstance: this,\n parent: null,\n uiState: this._initialUiState,\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 this.mainHelper!.search();\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 onStateChange = () => {\n const nextUiState = this.mainIndex.getWidgetState({});\n\n this.middleware.forEach(m => {\n m.onStateChange({\n state: 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","export default '4.3.0';\n","import {\n highlight,\n snippet,\n HighlightOptions,\n SnippetOptions,\n insights,\n} from '../helpers';\nimport { Hit, InsightsClientMethod, InsightsClientPayload } from '../types';\n\ntype HoganRenderer = (value: any) => string;\n\ninterface HoganHelpers {\n formatNumber: (value: number, render: HoganRenderer) => string;\n highlight: (options: string, render: HoganRenderer) => string;\n snippet: (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 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 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 simpleStateMapping from '../lib/stateMappings/simple';\nimport historyRouter from '../lib/routers/history';\nimport { Index } from '../widgets/index/index';\nimport { Middleware } from '.';\nimport { Router, StateMapping, UiState } from '../types';\n\nconst walk = (current: Index, callback: (index: Index) => void) => {\n callback(current);\n current\n .getWidgets()\n .filter(function(widget): widget is Index {\n return widget.$$type === 'ais.index';\n })\n .forEach(innerIndex => {\n walk(innerIndex, callback);\n });\n};\n\nexport interface RouterProps {\n router?: Router;\n stateMapping?: StateMapping;\n}\n\nexport type RoutingManager = (\n props?: RouterProps\n) => Middleware;\n\nexport const createRouter: 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.getWidgetState({})\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 return {\n onStateChange({ state }) {\n const route = stateMapping.stateToRoute(state);\n\n router.write(route);\n },\n\n subscribe() {\n router.onUpdate(route => {\n const uiState = stateMapping.routeToState(route);\n\n walk(instantSearchInstance.mainIndex, current => {\n const widgets = current.getWidgets();\n const indexUiState = uiState[current.getIndexId()] || {};\n\n const searchParameters = widgets.reduce((parameters, widget) => {\n if (!widget.getWidgetSearchParameters) {\n return parameters;\n }\n\n return widget.getWidgetSearchParameters(parameters, {\n uiState: indexUiState,\n });\n }, current.getHelper()!.state);\n\n current\n .getHelper()!\n .overrideStateWithoutTriggeringChangeEvent(searchParameters);\n\n instantSearchInstance.scheduleSearch();\n });\n });\n },\n\n unsubscribe() {\n router.dispose();\n },\n };\n };\n};\n","import {\n checkRendering,\n clearRefinements,\n getRefinements,\n createDocumentationMessageGenerator,\n noop,\n uniq,\n mergeSearchParameters,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'clear-refinements',\n connector: true,\n});\n\n/**\n * @typedef {Object} CustomClearRefinementsWidgetOptions\n * @property {string[]} [includedAttributes = []] The attributes to include in the refinements to clear (all by default). Cannot be used with `excludedAttributes`.\n * @property {string[]} [excludedAttributes = ['query']] The attributes to exclude from the refinements to clear. Cannot be used with `includedAttributes`.\n * @property {function(object[]):object[]} [transformItems] Function to transform the items passed to the templates.\n */\n\n/**\n * @typedef {Object} ClearRefinementsRenderingOptions\n * @property {function} refine Triggers the clear of all the currently refined values.\n * @property {boolean} hasRefinements Indicates if search state is refined.\n * @property {function} createURL Creates a url for the next state when refinements are cleared.\n * @property {Object} widgetParams All original `CustomClearRefinementsWidgetOptions` forwarded to the `renderFn`.\n */\n\n/**\n * **ClearRefinements** connector provides the logic to build a custom widget that will give the user\n * the ability to reset the search state.\n *\n * This connector provides a `refine` function to remove the current refined facets.\n *\n * The behaviour of this function can be changed with widget options. If `clearsQuery`\n * is set to `true`, `refine` will also clear the query and `excludedAttributes` can\n * prevent certain attributes from being cleared.\n *\n * @type {Connector}\n * @param {function(ClearRefinementsRenderingOptions, boolean)} renderFn Rendering function for the custom **ClearRefinements** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomClearRefinementsWidgetOptions)} Re-usable widget factory for a custom **ClearRefinements** widget.\n * @example\n * // custom `renderFn` to render the custom ClearRefinements widget\n * function renderFn(ClearRefinementsRenderingOptions, isFirstRendering) {\n * var containerNode = ClearRefinementsRenderingOptions.widgetParams.containerNode;\n * if (isFirstRendering) {\n * var markup = $('');\n * containerNode.append(markup);\n *\n * markup.on('click', function(event) {\n * event.preventDefault();\n * ClearRefinementsRenderingOptions.refine();\n * })\n * }\n *\n * var clearRefinementsCTA = containerNode.find('#custom-clear-all');\n * clearRefinementsCTA.attr('disabled', !ClearRefinementsRenderingOptions.hasRefinements)\n * };\n *\n * // connect `renderFn` to ClearRefinements logic\n * var customClearRefinementsWidget = instantsearch.connectors.connectClearRefinements(renderFn);\n *\n * // mount widget on the page\n * search.addWidgets([\n * customClearRefinementsWidget({\n * containerNode: $('#custom-clear-all-container'),\n * })\n * ]);\n */\nexport default function connectClearRefinements(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => {\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 const {\n includedAttributes = [],\n excludedAttributes = ['query'],\n transformItems = items => items,\n } = widgetParams;\n\n const connectorState = {\n refine: noop,\n createURL: () => '',\n };\n\n const cachedRefine = () => connectorState.refine();\n const cachedCreateURL = () => connectorState.createURL();\n\n return {\n $$type: 'ais.clearRefinements',\n\n init({ instantSearchInstance }) {\n renderFn(\n {\n hasRefinements: false,\n refine: cachedRefine,\n createURL: cachedCreateURL,\n instantSearchInstance,\n widgetParams,\n },\n true\n );\n },\n\n render({ scopedResults, createURL, instantSearchInstance }) {\n const attributesToClear = scopedResults.reduce(\n (results, scopedResult) => {\n return results.concat(\n getAttributesToClear({\n scopedResult,\n includedAttributes,\n excludedAttributes,\n transformItems,\n })\n );\n },\n []\n );\n\n connectorState.refine = () => {\n attributesToClear.forEach(({ helper: indexHelper, items }) => {\n indexHelper\n .setState(\n clearRefinements({\n helper: indexHelper,\n attributesToClear: items,\n })\n )\n .search();\n });\n };\n\n connectorState.createURL = () =>\n createURL(\n mergeSearchParameters(\n ...attributesToClear.map(({ helper: indexHelper, items }) => {\n return clearRefinements({\n helper: indexHelper,\n attributesToClear: items,\n });\n })\n )\n );\n\n renderFn(\n {\n hasRefinements: attributesToClear.some(\n attributeToClear => attributeToClear.items.length > 0\n ),\n refine: cachedRefine,\n createURL: cachedCreateURL,\n instantSearchInstance,\n widgetParams,\n },\n false\n );\n },\n\n dispose() {\n unmountFn();\n },\n };\n };\n}\n\nfunction getAttributesToClear({\n scopedResult,\n includedAttributes,\n excludedAttributes,\n transformItems,\n}) {\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","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 {\n Unmounter,\n WidgetFactory,\n Renderer,\n RendererOptions,\n} from '../../types';\n\nexport type ConnectorRefinement = {\n attribute: string;\n type: string;\n value: string | number;\n label: string;\n operator?: string;\n count?: number;\n exhaustive?: boolean;\n};\n\ninterface ConnectorNumericRefinement extends ConnectorRefinement {\n value: number;\n}\n\nexport type Item = {\n indexName: string;\n attribute: string;\n label: string;\n refinements: ItemRefinement[];\n refine(refinement: ItemRefinement): void;\n};\n\nexport type ItemRefinement = {\n type:\n | 'facet'\n | 'exclude'\n | 'disjunctive'\n | 'hierarchical'\n | 'numeric'\n | 'query'\n | 'tag';\n attribute: string;\n value: string;\n label: string;\n operator?: string;\n count?: number;\n exhaustive?: boolean;\n};\n\ninterface 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 * The attributes to exclude from the widget.\n * Cannot be used with `includedAttributes`.\n *\n * @default `['query']`\n */\n excludedAttributes?: string[];\n /**\n * Receives the items, and is called before displaying them.\n * Should return a new array with the same shape as the original array.\n * Useful for mapping over the items to transform, and remove or reorder them.\n */\n transformItems?: (items: Item[]) => any;\n}\n\nexport interface CurrentRefinementsRendererOptions<\n TCurrentRefinementsWidgetParams\n> extends RendererOptions {\n items: Item[];\n refine(refinement: ItemRefinement): void;\n createURL(state: ItemRefinement): string;\n}\n\nexport type CurrentRefinementsRenderer<\n TCurrentRefinementsWidgetParams\n> = Renderer<\n CurrentRefinementsRendererOptions<\n CurrentRefinementsConnectorParams & TCurrentRefinementsWidgetParams\n >\n>;\n\nexport type CurrentRefinementsWidgetFactory<\n TCurrentRefinementsWidgetParams\n> = WidgetFactory<\n CurrentRefinementsConnectorParams & TCurrentRefinementsWidgetParams\n>;\n\nexport type CurrentRefinementsConnector = (\n render: CurrentRefinementsRenderer,\n unmount?: Unmounter\n) => CurrentRefinementsWidgetFactory;\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'current-refinements',\n connector: true,\n});\n\nconst connectCurrentRefinements: CurrentRefinementsConnector = (\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: Item[]) => items,\n } = widgetParams || ({} as typeof widgetParams);\n\n return {\n $$type: 'ais.currentRefinements',\n\n init({ helper, createURL, instantSearchInstance }) {\n const items = transformItems(\n getItems({\n results: {} as SearchResults,\n helper,\n includedAttributes,\n excludedAttributes,\n })\n );\n\n renderFn(\n {\n items,\n refine: refinement => clearRefinement(helper, refinement),\n createURL: refinement =>\n createURL(clearRefinementFromState(helper.state, refinement)),\n instantSearchInstance,\n widgetParams,\n },\n true\n );\n },\n\n render({ scopedResults, helper, createURL, instantSearchInstance }) {\n const items = scopedResults.reduce((results, scopedResult) => {\n return results.concat(\n transformItems(\n getItems({\n results: scopedResult.results,\n helper: scopedResult.helper,\n includedAttributes,\n excludedAttributes,\n })\n )\n );\n }, []);\n\n renderFn(\n {\n items,\n refine: refinement => clearRefinement(helper, refinement),\n createURL: refinement =>\n createURL(clearRefinementFromState(helper.state, refinement)),\n instantSearchInstance,\n widgetParams,\n },\n false\n );\n },\n\n dispose() {\n unmountFn();\n },\n };\n };\n};\n\nfunction getItems({\n results,\n helper,\n includedAttributes,\n excludedAttributes,\n}: {\n results: SearchResults;\n helper: AlgoliaSearchHelper;\n includedAttributes: CurrentRefinementsConnectorParams['includedAttributes'];\n excludedAttributes: CurrentRefinementsConnectorParams['excludedAttributes'];\n}): Item[] {\n const clearsQuery =\n (includedAttributes || []).indexOf('query') !== -1 ||\n (excludedAttributes || []).indexOf('query') === -1;\n\n const filterFunction = includedAttributes\n ? (item: ConnectorRefinement) =>\n includedAttributes.indexOf(item.attribute) !== -1\n : (item: ConnectorRefinement) =>\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(\n (item: Item) => item.attribute !== currentItem.attribute\n ),\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'\n ? (a as ConnectorNumericRefinement).value -\n (b as ConnectorNumericRefinement).value\n : 0\n ),\n refine: (refinement: ItemRefinement) =>\n clearRefinement(helper, refinement),\n },\n ],\n []\n );\n}\n\nfunction clearRefinementFromState(\n state: SearchParameters,\n refinement: ItemRefinement\n): SearchParameters {\n switch (refinement.type) {\n case 'facet':\n return state.removeFacetRefinement(\n refinement.attribute,\n refinement.value\n );\n case 'disjunctive':\n return state.removeDisjunctiveFacetRefinement(\n refinement.attribute,\n refinement.value\n );\n case 'hierarchical':\n return state.removeHierarchicalFacetRefinement(refinement.attribute);\n case 'exclude':\n return state.removeExcludeRefinement(\n refinement.attribute,\n refinement.value\n );\n case 'numeric':\n return state.removeNumericRefinement(\n refinement.attribute,\n refinement.operator,\n refinement.value\n );\n case 'tag':\n return state.removeTagRefinement(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: ItemRefinement\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(refinement: Refinement): ConnectorRefinement {\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: ConnectorRefinement = {\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 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} CustomHierarchicalMenuWidgetOptions\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 `CustomHierarchicalMenuWidgetOptions` 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)} renderFn Rendering function for the custom **HierarchicalMenu** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomHierarchicalMenuWidgetOptions)} 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\n return {\n $$type: 'ais.hierarchicalMenu',\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 this.toggleShowMore();\n },\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({ helper, createURL, instantSearchInstance }) {\n this.cachedToggleShowMore = this.cachedToggleShowMore.bind(this);\n this._refine = function(facetValue) {\n helper.toggleRefinement(hierarchicalFacetName, facetValue).search();\n };\n\n // Bind createURL to this specific attribute\n function _createURL(facetValue) {\n return createURL(\n helper.state.toggleRefinement(hierarchicalFacetName, facetValue)\n );\n }\n\n renderFn(\n {\n items: [],\n createURL: _createURL,\n refine: this._refine,\n instantSearchInstance,\n widgetParams,\n isShowingMore: false,\n toggleShowMore: this.cachedToggleShowMore,\n canToggleShowMore: false,\n },\n true\n );\n },\n\n _prepareFacetValues(facetValues, state) {\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, state);\n }\n return { ...subValue, label, value };\n });\n },\n\n render(renderOptions) {\n const {\n results,\n state,\n createURL,\n instantSearchInstance,\n } = renderOptions;\n\n const facetValues =\n results.getFacetValues(hierarchicalFacetName, { sortBy }).data || [];\n const items = transformItems(\n this._prepareFacetValues(facetValues),\n state\n );\n\n // Bind createURL to this specific attribute\n function _createURL(facetValue) {\n return createURL(\n state.toggleRefinement(hierarchicalFacetName, facetValue)\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 const hasExhaustiveItems =\n maxValuesPerFacetConfig > currentLimit\n ? facetValues.length <= currentLimit\n : facetValues.length < currentLimit;\n\n this.toggleShowMore = this.createToggleShowMore(renderOptions);\n\n renderFn(\n {\n items,\n refine: this._refine,\n createURL: _createURL,\n instantSearchInstance,\n widgetParams,\n isShowingMore: this.isShowingMore,\n toggleShowMore: this.cachedToggleShowMore,\n canToggleShowMore:\n showMore && (this.isShowingMore || !hasExhaustiveItems),\n },\n false\n );\n },\n\n // eslint-disable-next-line valid-jsdoc\n /**\n * @param {Object} param0\n * @param {import('algoliasearch-helper').SearchParameters} param0.state\n */\n dispose({ state }) {\n unmountFn();\n\n return state\n .removeHierarchicalFacet(hierarchicalFacetName)\n .setQueryParameter('maxValuesPerFacet', undefined);\n },\n\n getWidgetState(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 escapeHits, { TAG_PLACEHOLDER } from '../../lib/escape-highlight';\nimport {\n checkRendering,\n createDocumentationMessageGenerator,\n addAbsolutePosition,\n addQueryID,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'hits',\n connector: true,\n});\n\n/**\n * @typedef {Object} HitsRenderingOptions\n * @property {Object[]} hits The matched hits from Algolia API.\n * @property {Object} results The complete results response from Algolia API.\n * @property {Object} widgetParams All original widget options forwarded to the `renderFn`.\n */\n\n/**\n * @typedef {Object} CustomHitsWidgetOptions\n * @property {boolean} [escapeHTML = true] Whether to escape HTML tags from `hits[i]._highlightResult`.\n * @property {function(Object[]):Object[]} [transformItems] Function to transform the items passed to the templates.\n */\n\n/**\n * **Hits** connector provides the logic to create custom widgets that will render the results retrieved from Algolia.\n * @type {Connector}\n * @param {function(HitsRenderingOptions, boolean)} renderFn Rendering function for the custom **Hits** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomHitsWidgetOptions)} Re-usable widget factory for a custom **Hits** widget.\n * @example\n * // custom `renderFn` to render the custom Hits widget\n * function renderFn(HitsRenderingOptions) {\n * HitsRenderingOptions.widgetParams.containerNode.html(\n * HitsRenderingOptions.hits.map(function(hit) {\n * return '
' + hit._highlightResult.name.value + '
';\n * })\n * );\n * }\n *\n * // connect `renderFn` to Hits logic\n * var customHits = instantsearch.connectors.connectHits(renderFn);\n *\n * // mount widget on the page\n * search.addWidgets([\n * customHits({\n * containerNode: $('#custom-hits-container'),\n * })\n * ]);\n */\nexport default function connectHits(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => {\n const { escapeHTML = true, transformItems = items => items } = widgetParams;\n\n return {\n $$type: 'ais.hits',\n\n init({ instantSearchInstance }) {\n renderFn(\n {\n hits: [],\n results: undefined,\n instantSearchInstance,\n widgetParams,\n },\n true\n );\n },\n\n render({ results, instantSearchInstance }) {\n if (escapeHTML && results.hits.length > 0) {\n results.hits = escapeHits(results.hits);\n }\n\n const initialEscaped = results.hits.__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.__escaped = initialEscaped;\n\n renderFn(\n {\n hits: results.hits,\n results,\n instantSearchInstance,\n widgetParams,\n },\n false\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","import { SearchResults } from 'algoliasearch-helper';\nimport { uniq, find, createDocumentationMessageGenerator } from '../utils';\nimport {\n Hits,\n InsightsClient,\n InsightsClientMethod,\n InsightsClientPayload,\n InsightsClientWrapper,\n Renderer,\n RendererOptions,\n Unmounter,\n WidgetFactory,\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 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\ntype Connector = (\n renderFn: Renderer,\n unmountFn: Unmounter\n) => WidgetFactory;\n\nexport default function withInsights(\n connector: Connector\n): Connector {\n const wrapRenderFn = (\n renderFn: Renderer>\n ): Renderer> => (\n renderOptions: RendererOptions,\n isFirstRender: boolean\n ) => {\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: Renderer>, unmountFn: Unmounter) =>\n 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';\n\ntype WithInsightsListenerProps = {\n [key: string]: unknown;\n insights: InsightsClientWrapper;\n};\n\nconst findInsightsTarget = (\n startElement: HTMLElement | null,\n endElement: HTMLElement | null\n): HTMLElement | null => {\n let element: HTMLElement | null = startElement;\n while (element && !hasDataAttributes(element)) {\n if (element === endElement) {\n return null;\n }\n element = element.parentElement;\n }\n return element;\n};\n\nconst insightsListener = (BaseComponent: any) => {\n function WithInsightsListener(props: WithInsightsListenerProps) {\n const handleClick = (event: MouseEvent): void => {\n const insightsTarget = findInsightsTarget(\n event.target as HTMLElement | null,\n event.currentTarget as HTMLElement | null\n );\n\n if (!insightsTarget) return;\n\n const { method, payload } = readDataAttributes(insightsTarget);\n\n props.insights(method, payload);\n };\n\n return (\n
\n \n
\n );\n }\n\n return WithInsightsListener;\n};\n\nexport default insightsListener;\n","import { withInsights } from '../../lib/insights';\nimport connectHits from './connectHits';\n\nconst connectHitsWithInsights = withInsights(connectHits);\n\nexport default connectHitsWithInsights;\n","import {\n checkRendering,\n warning,\n createDocumentationMessageGenerator,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'hits-per-page',\n connector: true,\n});\n\n/**\n * @typedef {Object} HitsPerPageRenderingOptionsItem\n * @property {number} value Number of hits to display per page.\n * @property {string} label Label to display in the option.\n * @property {boolean} isRefined Indicates if it's the current refined value.\n */\n\n/**\n * @typedef {Object} HitsPerPageWidgetOptionsItem\n * @property {number} value Number of hits to display per page.\n * @property {string} label Label to display in the option.\n * @property {boolean} default The default hits per page on first search.\n */\n\n/**\n * @typedef {Object} HitsPerPageRenderingOptions\n * @property {HitsPerPageRenderingOptionsItem[]} items Array of objects defining the different values and labels.\n * @property {function(item.value)} createURL Creates the URL for a single item name in the list.\n * @property {function(number)} refine Sets the number of hits per page and trigger a search.\n * @property {boolean} hasNoResults `true` if the last search contains no result.\n * @property {Object} widgetParams Original `HitsPerPageWidgetOptions` forwarded to `renderFn`.\n */\n\n/**\n * @typedef {Object} HitsPerPageWidgetOptions\n * @property {HitsPerPageWidgetOptionsItem[]} items Array of objects defining the different values and labels.\n * @property {function(object[]):object[]} [transformItems] Function to transform the items passed to the templates.\n */\n\n/**\n * **HitsPerPage** connector provides the logic to create custom widget that will\n * allow a user to choose to display more or less results from Algolia.\n *\n * This connector provides a `refine()` function to change the hits per page configuration and trigger a new search.\n * @type {Connector}\n * @param {function(HitsPerPageRenderingOptions, boolean)} renderFn Rendering function for the custom **HitsPerPage** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(HitsPerPageWidgetOptions)} Re-usable widget factory for a custom **HitsPerPage** widget.\n * @example\n * // custom `renderFn` to render the custom HitsPerPage widget\n * function renderFn(HitsPerPageRenderingOptions, isFirstRendering) {\n * var containerNode = HitsPerPageRenderingOptions.widgetParams.containerNode\n * var items = HitsPerPageRenderingOptions.items\n * var refine = HitsPerPageRenderingOptions.refine\n *\n * if (isFirstRendering) {\n * var markup = '';\n * containerNode.append(markup);\n * }\n *\n * const itemsHTML = items.map(({value, label, isRefined}) => `\n * \n * ${label}\n * \n * `);\n *\n * containerNode\n * .find('select')\n * .html(itemsHTML);\n *\n * containerNode\n * .find('select')\n * .off('change')\n * .on('change', e => { refine(e.target.value); });\n * }\n *\n * // connect `renderFn` to HitsPerPage logic\n * var customHitsPerPage = instantsearch.connectors.connectHitsPerPage(renderFn);\n *\n * // mount widget on the page\n * search.addWidgets([\n * customHitsPerPage({\n * containerNode: $('#custom-hits-per-page-container'),\n * items: [\n * {value: 6, label: '6 per page', default: true},\n * {value: 12, label: '12 per page'},\n * {value: 24, label: '24 per page'},\n * ],\n * })\n * ]);\n */\nexport default function connectHitsPerPage(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n\n return (widgetParams = {}) => {\n const { items: userItems, transformItems = items => items } = 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 return {\n $$type: 'ais.hitsPerPage',\n\n init({ helper, createURL, state, instantSearchInstance }) {\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://community.algolia.com/instantsearch.js/v2/widgets/configure.html\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 = [{ value: '', label: '' }, ...items];\n }\n\n this.setHitsPerPage = value =>\n !value && value !== 0\n ? helper.setQueryParameter('hitsPerPage', undefined).search()\n : helper.setQueryParameter('hitsPerPage', value).search();\n\n this.createURL = helperState => value =>\n createURL(\n helperState.setQueryParameter(\n 'hitsPerPage',\n !value && value !== 0 ? undefined : value\n )\n );\n\n renderFn(\n {\n items: transformItems(this._normalizeItems(state)),\n refine: this.setHitsPerPage,\n createURL: this.createURL(helper.state),\n hasNoResults: true,\n widgetParams,\n instantSearchInstance,\n },\n true\n );\n },\n\n render({ state, results, instantSearchInstance }) {\n const hasNoResults = results.nbHits === 0;\n\n renderFn(\n {\n items: transformItems(this._normalizeItems(state)),\n refine: this.setHitsPerPage,\n createURL: this.createURL(state),\n hasNoResults,\n widgetParams,\n instantSearchInstance,\n },\n false\n );\n },\n\n _normalizeItems({ hitsPerPage }) {\n return items.map(item => ({\n ...item,\n isRefined: Number(item.value) === Number(hitsPerPage),\n }));\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.setQueryParameter('hitsPerPage', undefined);\n },\n\n getWidgetState(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","import escapeHits, { TAG_PLACEHOLDER } from '../../lib/escape-highlight';\nimport {\n AlgoliaSearchHelper as Helper,\n SearchParameters,\n} from 'algoliasearch-helper';\nimport {\n Renderer,\n RendererOptions,\n WidgetFactory,\n Hits,\n Unmounter,\n} from '../../types';\nimport {\n checkRendering,\n createDocumentationMessageGenerator,\n isEqual,\n addAbsolutePosition,\n addQueryID,\n noop,\n} from '../../lib/utils';\nimport { InfiniteHitsRendererWidgetParams } from '../../widgets/infinite-hits/infinite-hits';\n\nexport type InfiniteHitsConnectorParams = Partial<\n InfiniteHitsRendererWidgetParams\n>;\n\nexport interface InfiniteHitsRendererOptions\n extends RendererOptions {\n showPrevious: () => void;\n showMore: () => void;\n isFirstPage: boolean;\n isLastPage: boolean;\n}\n\nexport type InfiniteHitsRenderer = Renderer<\n InfiniteHitsRendererOptions<\n InfiniteHitsConnectorParams & TInfiniteHitsWidgetParams\n >\n>;\n\nexport type InfiniteHitsWidgetFactory<\n TInfiniteHitsWidgetParams\n> = WidgetFactory;\n\nexport type InfiniteHitsConnector = (\n render: InfiniteHitsRenderer,\n unmount?: Unmounter\n) => InfiniteHitsWidgetFactory;\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'infinite-hits',\n connector: true,\n});\n\nconst connectInfiniteHits: InfiniteHitsConnector = (\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 showPrevious: hasShowPrevious = false,\n } = widgetParams || ({} as typeof widgetParams);\n let hitsCache: Hits = [];\n let firstReceivedPage = Infinity;\n let lastReceivedPage = -1;\n let prevState: Partial;\n let showPrevious: () => void;\n let showMore: () => void;\n\n const getShowPrevious = (helper: Helper): (() => 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: firstReceivedPage - 1,\n })\n .search();\n };\n const getShowMore = (helper: Helper): (() => void) => () => {\n helper.setPage(lastReceivedPage + 1).search();\n };\n const filterEmptyRefinements = (refinements = {}) => {\n return Object.keys(refinements)\n .filter(key =>\n Array.isArray(refinements[key])\n ? refinements[key].length\n : Object.keys(refinements[key]).length\n )\n .reduce((obj, key) => {\n obj[key] = refinements[key];\n return obj;\n }, {});\n };\n\n return {\n $$type: 'ais.infiniteHits',\n\n init({ instantSearchInstance, helper }) {\n showPrevious = getShowPrevious(helper);\n showMore = getShowMore(helper);\n firstReceivedPage = helper.state.page || 0;\n lastReceivedPage = helper.state.page || 0;\n\n renderFn(\n {\n hits: hitsCache,\n results: undefined,\n showPrevious,\n showMore,\n isFirstPage: firstReceivedPage === 0,\n isLastPage: true,\n instantSearchInstance,\n widgetParams,\n },\n true\n );\n },\n\n render({ results, state, instantSearchInstance }) {\n // Reset cache and received pages if anything changes in the\n // search state, except for the page.\n //\n // We're doing this to \"reset\" the widget if a refinement or the\n // query changes between renders, but we want to keep it as is\n // if we only change pages.\n const {\n page = 0,\n facets,\n hierarchicalFacets,\n disjunctiveFacets,\n maxValuesPerFacet,\n ...currentState\n } = state;\n\n currentState.facetsRefinements = filterEmptyRefinements(\n currentState.facetsRefinements\n );\n currentState.hierarchicalFacetsRefinements = filterEmptyRefinements(\n currentState.hierarchicalFacetsRefinements\n );\n currentState.disjunctiveFacetsRefinements = filterEmptyRefinements(\n currentState.disjunctiveFacetsRefinements\n );\n currentState.numericRefinements = filterEmptyRefinements(\n currentState.numericRefinements\n );\n\n if (!isEqual(currentState, prevState)) {\n hitsCache = [];\n firstReceivedPage = page;\n lastReceivedPage = page;\n prevState = currentState;\n }\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 (lastReceivedPage < page || !hitsCache.length) {\n hitsCache = [...hitsCache, ...results.hits];\n lastReceivedPage = page;\n } else if (firstReceivedPage > page) {\n hitsCache = [...results.hits, ...hitsCache];\n firstReceivedPage = page;\n }\n\n const isFirstPage = firstReceivedPage === 0;\n const isLastPage = results.nbPages <= results.page + 1;\n\n renderFn(\n {\n hits: hitsCache,\n results,\n showPrevious,\n showMore,\n isFirstPage,\n isLastPage,\n instantSearchInstance,\n widgetParams,\n },\n false\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 getWidgetState(uiState, { searchParameters }) {\n const page = searchParameters.page || 0;\n\n if (!hasShowPrevious || !page) {\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 from './connectInfiniteHits';\n\nconst connectInfiniteHitsWithInsights = withInsights(connectInfiniteHits);\n\nexport default connectInfiniteHitsWithInsights;\n","import {\n checkRendering,\n createDocumentationMessageGenerator,\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} CustomMenuWidgetOptions\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 `CustomMenuWidgetOptions` 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(CustomMenuWidgetOptions)} 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 hasExhaustiveItems = true;\n let searchForFacetValues;\n let triggerRefine;\n\n const render = ({\n items,\n state,\n createURL,\n helperSpecializedSearchFacetValues,\n refine,\n isFromSearch,\n isFirstSearch,\n isShowingMore,\n toggleShowMore,\n instantSearchInstance,\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 helperSpecializedSearchFacetValues &&\n helperSpecializedSearchFacetValues(\n state,\n createURL,\n helperSpecializedSearchFacetValues,\n refine,\n instantSearchInstance,\n isShowingMore\n );\n\n const canShowLess =\n isShowingMore && lastResultsFromMainSearch.length > limit;\n const canShowMore = showMore && !isFromSearch && !hasExhaustiveItems;\n\n const canToggleShowMore = canShowLess || canShowMore;\n\n renderFn(\n {\n createURL: _createURL,\n items,\n refine,\n searchForItems: searchFacetValues,\n instantSearchInstance,\n isFromSearch,\n canRefine: isFromSearch || items.length > 0,\n widgetParams,\n isShowingMore,\n canToggleShowMore,\n toggleShowMore,\n hasExhaustiveItems,\n },\n isFirstSearch\n );\n };\n\n /* eslint-disable max-params */\n const createSearchForFacetValues = (helper, toggleShowMore) => (\n state,\n createURL,\n helperSpecializedSearchFacetValues,\n toggleRefinement,\n instantSearchInstance,\n isShowingMore\n ) => query => {\n if (query === '' && lastResultsFromMainSearch) {\n // render with previous data from the helper.\n render({\n items: lastResultsFromMainSearch,\n state,\n createURL,\n helperSpecializedSearchFacetValues,\n refine: toggleRefinement,\n isFromSearch: false,\n isFirstSearch: false,\n instantSearchInstance,\n toggleShowMore, // and yet it will be\n isShowingMore, // so we need to restore in the state of show more as well\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(attribute, query, getLimit(isShowingMore), tags)\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 render({\n items: normalizedFacetValues,\n state,\n createURL,\n helperSpecializedSearchFacetValues,\n refine: toggleRefinement,\n isFromSearch: true,\n isFirstSearch: false,\n instantSearchInstance,\n isShowingMore,\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 this.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({ helper, createURL, instantSearchInstance }) {\n this.cachedToggleShowMore = this.cachedToggleShowMore.bind(this);\n\n triggerRefine = facetValue =>\n helper.toggleRefinement(attribute, facetValue).search();\n\n searchForFacetValues = createSearchForFacetValues(\n helper,\n this.cachedToggleShowMore\n );\n\n render({\n items: [],\n state: helper.state,\n createURL,\n helperSpecializedSearchFacetValues: searchForFacetValues,\n refine: triggerRefine,\n isFromSearch: false,\n isFirstSearch: true,\n instantSearchInstance,\n isShowingMore: this.isShowingMore,\n toggleShowMore: this.cachedToggleShowMore,\n });\n },\n\n render(renderOptions) {\n const {\n results,\n state,\n createURL,\n instantSearchInstance,\n } = renderOptions;\n\n const facetValues = results.getFacetValues(attribute, { sortBy }) || [];\n const items = transformItems(\n facetValues.slice(0, this.getLimit()).map(formatItems)\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 = items;\n\n this.toggleShowMore = this.createToggleShowMore(renderOptions);\n\n render({\n items,\n state,\n createURL,\n helperSpecializedSearchFacetValues: searchForFacetValues,\n refine: triggerRefine,\n isFromSearch: false,\n isFirstSearch: false,\n instantSearchInstance,\n isShowingMore: this.isShowingMore,\n toggleShowMore: this.cachedToggleShowMore,\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 getWidgetState(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} CustomSearchBoxWidgetOptions\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 `CustomSearchBoxWidgetOptions` 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(CustomSearchBoxWidgetOptions)} 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('');\n helper.search();\n };\n }\n\n return {\n $$type: 'ais.searchBox',\n\n _clear() {},\n\n _cachedClear() {\n this._clear();\n },\n\n init({ helper, instantSearchInstance }) {\n this._cachedClear = this._cachedClear.bind(this);\n this._clear = clear(helper);\n\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 renderFn(\n {\n query: helper.state.query || '',\n refine: this._refine,\n clear: this._cachedClear,\n widgetParams,\n instantSearchInstance,\n },\n true\n );\n },\n\n render({ helper, instantSearchInstance, searchMetadata }) {\n this._clear = clear(helper);\n\n renderFn(\n {\n query: helper.state.query || '',\n refine: this._refine,\n clear: this._cachedClear,\n widgetParams,\n instantSearchInstance,\n isSearchStalled: searchMetadata.isSearchStalled,\n },\n false\n );\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.setQueryParameter('query', undefined);\n },\n\n getWidgetState(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} CustomSortByWidgetOptions\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 `CustomSortByWidgetOptions` 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(CustomSortByWidgetOptions)} 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({ helper, instantSearchInstance, parent }) {\n const currentIndex = helper.state.index;\n const isCurrentIndexInItems = find(\n items,\n item => item.value === currentIndex\n );\n\n this.initialIndex = parent.getIndexName();\n this.setIndex = indexName => {\n helper.setIndex(indexName).search();\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 currentRefinement: currentIndex,\n options: transformItems(items),\n refine: this.setIndex,\n hasNoResults: true,\n widgetParams,\n instantSearchInstance,\n },\n true\n );\n },\n\n render({ helper, results, instantSearchInstance }) {\n renderFn(\n {\n currentRefinement: helper.state.index,\n options: transformItems(items),\n refine: this.setIndex,\n hasNoResults: results.nbHits === 0,\n widgetParams,\n instantSearchInstance,\n },\n false\n );\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.setIndex(this.initialIndex);\n },\n\n getWidgetState(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 createDocumentationMessageGenerator,\n range,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'rating-menu',\n connector: true,\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} CustomStarRatingWidgetOptions\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 `CustomStarRatingWidgetOptions` 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(CustomStarRatingWidgetOptions)} 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\n if (!attribute) {\n throw new Error(withUsage('The `attribute` option is required.'));\n }\n\n return {\n $$type: 'ais.ratingMenu',\n\n init({ helper, createURL, instantSearchInstance }) {\n this._toggleRefinement = this._toggleRefinement.bind(this, helper);\n this._createURL = state => facetValue =>\n createURL(state.toggleRefinement(attribute, facetValue));\n\n renderFn(\n {\n instantSearchInstance,\n items: [],\n hasNoResults: true,\n refine: this._toggleRefinement,\n createURL: this._createURL(helper.state),\n widgetParams,\n },\n true\n );\n },\n\n render({ helper, results, state, instantSearchInstance }) {\n const facetValues = [];\n const allValues = {};\n for (let v = max; v >= 0; --v) {\n allValues[v] = 0;\n }\n (results.getFacetValues(attribute) || []).forEach(facet => {\n const val = Math.round(facet.name);\n if (!val || val > max) {\n return;\n }\n for (let v = val; v >= 1; --v) {\n allValues[v] += facet.count;\n }\n });\n const refinedStar = this._getRefinedStar(helper.state);\n for (let star = max - 1; star >= 1; --star) {\n const count = allValues[star];\n if (refinedStar && star !== refinedStar && count === 0) {\n // skip count==0 when at least 1 refinement is enabled\n // eslint-disable-next-line no-continue\n continue;\n }\n const stars = [];\n for (let i = 1; i <= max; ++i) {\n stars.push(i <= star);\n }\n facetValues.push({\n stars,\n name: String(star),\n value: String(star),\n count,\n isRefined: refinedStar === star,\n });\n }\n\n renderFn(\n {\n instantSearchInstance,\n items: facetValues,\n hasNoResults: results.nbHits === 0,\n refine: this._toggleRefinement,\n createURL: this._createURL(state),\n widgetParams,\n },\n false\n );\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.removeDisjunctiveFacet(attribute);\n },\n\n getWidgetState(uiState, { searchParameters }) {\n const value = this._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 disjunctiveFacetsRefinements: {\n ...withDisjunctiveFacet.disjunctiveFacetsRefinements,\n [attribute]: [],\n },\n });\n }\n\n return range({ start: Number(value), end: max + 1 }).reduce(\n (parameters, number) =>\n parameters.addDisjunctiveFacetRefinement(attribute, number),\n withDisjunctiveFacet\n );\n },\n\n _toggleRefinement(helper, facetValue) {\n const isRefined =\n this._getRefinedStar(helper.state) === Number(facetValue);\n helper.removeDisjunctiveFacetRefinement(attribute);\n if (!isRefined) {\n for (let val = Number(facetValue); val <= max; ++val) {\n helper.addDisjunctiveFacetRefinement(attribute, val);\n }\n }\n helper.search();\n },\n\n _getRefinedStar(state) {\n const refinements = state.getDisjunctiveRefinements(attribute);\n\n if (!refinements.length) {\n return undefined;\n }\n\n return Math.min(...refinements.map(Number));\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 `CustomStatsWidgetOptions` forwarded to the `renderFn`.\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} 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({ helper, instantSearchInstance }) {\n renderFn(\n {\n instantSearchInstance,\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 true\n );\n },\n\n render({ results, instantSearchInstance }) {\n renderFn(\n {\n instantSearchInstance,\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 false\n );\n },\n\n dispose() {\n unmountFn();\n },\n });\n}\n","import {\n checkRendering,\n escapeRefinement,\n unescapeRefinement,\n createDocumentationMessageGenerator,\n find,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'toggle-refinement',\n connector: true,\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} CustomToggleWidgetOptions\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 `CustomToggleWidgetOptions` 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(CustomToggleWidgetOptions)} 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 ? escapeRefinement(userOn) : undefined;\n const off = hasAnOffValue ? escapeRefinement(userOff) : undefined;\n\n return {\n $$type: 'ais.toggleRefinement',\n\n _toggleRefinement(helper, { isRefined } = {}) {\n // Checking\n if (!isRefined) {\n if (hasAnOffValue) {\n helper.removeDisjunctiveFacetRefinement(attribute, off);\n }\n helper.addDisjunctiveFacetRefinement(attribute, on);\n } else {\n // Unchecking\n helper.removeDisjunctiveFacetRefinement(attribute, on);\n if (hasAnOffValue) {\n helper.addDisjunctiveFacetRefinement(attribute, off);\n }\n }\n\n helper.search();\n },\n\n init({ state, helper, createURL, instantSearchInstance }) {\n this._createURL = isCurrentlyRefined => () =>\n createURL(\n state\n .removeDisjunctiveFacetRefinement(\n attribute,\n isCurrentlyRefined ? on : off\n )\n .addDisjunctiveFacetRefinement(\n attribute,\n isCurrentlyRefined ? off : on\n )\n );\n\n this.toggleRefinement = opts => {\n this._toggleRefinement(helper, opts);\n };\n\n const isRefined = state.isDisjunctiveFacetRefined(attribute, on);\n\n // no need to refine anything at init if no custom off values\n if (hasAnOffValue) {\n // Add filtering on the 'off' value if set\n if (!isRefined) {\n const currentPage = helper.state.page;\n helper\n .addDisjunctiveFacetRefinement(attribute, off)\n .setPage(currentPage);\n }\n }\n\n const onFacetValue = {\n isRefined,\n count: 0,\n };\n\n const offFacetValue = {\n isRefined: hasAnOffValue && !isRefined,\n count: 0,\n };\n\n const value = {\n name: attribute,\n isRefined,\n count: null,\n onFacetValue,\n offFacetValue,\n };\n\n renderFn(\n {\n value,\n createURL: this._createURL(value.isRefined),\n refine: this.toggleRefinement,\n instantSearchInstance,\n widgetParams,\n },\n true\n );\n },\n\n render({ helper, results, state, instantSearchInstance }) {\n const isRefined = helper.state.isDisjunctiveFacetRefined(attribute, on);\n const offValue = off === undefined ? false : off;\n const allFacetValues = results.getFacetValues(attribute) || [];\n\n const onData = find(\n allFacetValues,\n ({ name }) => name === unescapeRefinement(on)\n );\n const onFacetValue = {\n isRefined: onData !== undefined ? onData.isRefined : false,\n count: onData === undefined ? null : onData.count,\n };\n\n const offData = hasAnOffValue\n ? find(\n allFacetValues,\n ({ name }) => name === unescapeRefinement(offValue)\n )\n : undefined;\n const offFacetValue = {\n isRefined: offData !== undefined ? offData.isRefined : false,\n count:\n offData === undefined\n ? allFacetValues.reduce((total, { count }) => total + count, 0)\n : offData.count,\n };\n\n // what will we show by default,\n // if checkbox is not checked, show: [ ] free shipping (countWhenChecked)\n // if checkbox is checked, show: [x] free shipping (countWhenNotChecked)\n const nextRefinement = isRefined ? offFacetValue : onFacetValue;\n\n const value = {\n name: attribute,\n isRefined,\n count: nextRefinement === undefined ? null : nextRefinement.count,\n onFacetValue,\n offFacetValue,\n };\n\n renderFn(\n {\n value,\n state,\n createURL: this._createURL(value.isRefined),\n refine: this.toggleRefinement,\n helper,\n instantSearchInstance,\n widgetParams,\n },\n false\n );\n },\n\n dispose({ state }) {\n unmountFn();\n\n return state.removeDisjunctiveFacet(attribute);\n },\n\n getWidgetState(uiState, { searchParameters }) {\n const isRefined = searchParameters.isDisjunctiveFacetRefined(\n attribute,\n on\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 const withFacetConfiguration = searchParameters\n .clearRefinements(attribute)\n .addDisjunctiveFacet(attribute);\n\n const isRefined = Boolean(uiState.toggle && uiState.toggle[attribute]);\n\n if (isRefined) {\n return withFacetConfiguration.addDisjunctiveFacetRefinement(\n attribute,\n on\n );\n }\n\n // It's not refined with an `off` value\n if (hasAnOffValue) {\n return withFacetConfiguration.addDisjunctiveFacetRefinement(\n attribute,\n off\n );\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';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'breadcrumb',\n connector: true,\n});\n\n/**\n * @typedef {Object} BreadcrumbItem\n * @property {string} label Label of the category or subcategory.\n * @property {string} value Value of breadcrumb item.\n */\n\n/**\n * @typedef {Object} CustomBreadcrumbWidgetOptions\n * @property {string[]} attributes Attributes to use to generate the hierarchy of the breadcrumb.\n * @property {string} [rootPath = null] Prefix path to use if the first level is not the root level.\n * @property {function(object[]):object[]} [transformItems] Function to transform the items passed to the templates.\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 */\n\n/**\n * @typedef {Object} BreadcrumbRenderingOptions\n * @property {function(item.value): string} createURL Creates an url for the next state for a clicked item. The special value `null` is used for the `Home` (or root) item of the breadcrumb and will return an empty array.\n * @property {BreadcrumbItem[]} 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 `CustomBreadcrumbWidgetOptions` forwarded to the `renderFn`.\n */\n\n/**\n * **Breadcrumb** connector provides the logic to build a custom widget\n * that will give the user the ability to see the current path in a hierarchical facet.\n *\n * This is commonly used in websites that have a large amount of content organized in a hierarchical manner (usually e-commerce websites).\n * @type {Connector}\n * @param {function(BreadcrumbRenderingOptions, boolean)} renderFn Rendering function for the custom **Breadcrumb* widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function(CustomBreadcrumbWidgetOptions)} Re-usable widget factory for a custom **Breadcrumb** widget.\n */\nexport default function connectBreadcrumb(renderFn, unmountFn = noop) {\n checkRendering(renderFn, withUsage());\n return (widgetParams = {}) => {\n const {\n attributes,\n separator = ' > ',\n rootPath = null,\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 const [hierarchicalFacetName] = attributes;\n\n return {\n $$type: 'ais.breadcrumb',\n\n init({ createURL, helper, instantSearchInstance }) {\n this._createURL = facetValue => {\n if (!facetValue) {\n const breadcrumb = helper.getHierarchicalFacetBreadcrumb(\n hierarchicalFacetName\n );\n if (breadcrumb.length > 0) {\n return createURL(\n helper.state.toggleRefinement(\n hierarchicalFacetName,\n breadcrumb[0]\n )\n );\n }\n }\n return createURL(\n helper.state.toggleRefinement(hierarchicalFacetName, facetValue)\n );\n };\n\n this._refine = function(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.toggleRefinement(hierarchicalFacetName, facetValue).search();\n }\n };\n\n renderFn(\n {\n createURL: this._createURL,\n canRefine: false,\n instantSearchInstance,\n items: [],\n refine: this._refine,\n widgetParams,\n },\n true\n );\n },\n\n render({ instantSearchInstance, results, state }) {\n const [{ name: facetName }] = state.hierarchicalFacets;\n\n const facetValues = results.getFacetValues(facetName);\n const data = Array.isArray(facetValues.data) ? facetValues.data : [];\n const items = transformItems(shiftItemsValues(prepareItems(data)));\n\n renderFn(\n {\n canRefine: items.length > 0,\n createURL: this._createURL,\n instantSearchInstance,\n items,\n refine: this._refine,\n widgetParams,\n },\n false\n );\n },\n\n dispose() {\n unmountFn();\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","import {\n checkRendering,\n aroundLatLngToPosition,\n insideBoundingBoxToBoundingBox,\n createDocumentationMessageGenerator,\n noop,\n} from '../../lib/utils';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'geo-search',\n connector: true,\n});\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} CustomGeoSearchWidgetOptions\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 `CustomGeoSearchWidgetOptions` 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(CustomGeoSearchWidgetOptions)} 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 const init = initArgs => {\n const { state, helper, 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 items: [],\n position: getPositionFromState(state),\n currentRefinement: getCurrentRefinementFromState(state),\n refine: refine(helper),\n clearMapRefinement: clearMapRefinement(helper),\n isRefinedWithMap: isRefinedWithMap(state),\n toggleRefineOnMapMove,\n isRefineOnMapMove,\n setMapMoveSinceLastRefine,\n hasMapMoveSinceLastRefine,\n widgetParams,\n instantSearchInstance,\n },\n isFirstRendering\n );\n };\n\n const render = renderArgs => {\n const { results, 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 (positionChangedSinceLastRefine || boundingBoxChangedSinceLastRefine) {\n widgetState.hasMapMoveSinceLastRefine = false;\n }\n\n widgetState.lastRefinePosition = state.aroundLatLng || '';\n widgetState.lastRefineBoundingBox = state.insideBoundingBox || '';\n\n widgetState.internalToggleRefineOnMapMove = createInternalToggleRefinementOnMapMove(\n render,\n renderArgs\n );\n\n widgetState.internalSetMapMoveSinceLastRefine = createInternalSetMapMoveSinceLastRefine(\n render,\n renderArgs\n );\n\n const items = transformItems(results.hits.filter(hit => hit._geoloc));\n\n renderFn(\n {\n items,\n position: getPositionFromState(state),\n currentRefinement: getCurrentRefinementFromState(state),\n refine: refine(helper),\n clearMapRefinement: clearMapRefinement(helper),\n isRefinedWithMap: isRefinedWithMap(state),\n toggleRefineOnMapMove,\n isRefineOnMapMove,\n setMapMoveSinceLastRefine,\n hasMapMoveSinceLastRefine,\n widgetParams,\n instantSearchInstance,\n },\n isFirstRendering\n );\n };\n\n return {\n $$type: 'ais.geoSearch',\n\n init,\n\n render,\n\n dispose({ state }) {\n unmountFn();\n\n return state.setQueryParameter('insideBoundingBox', undefined);\n },\n\n getWidgetState(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';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'powered-by',\n connector: true,\n});\n\n/**\n * @typedef {Object} PoweredByWidgetOptions\n * @property {string} [theme] The theme of the logo (\"light\" or \"dark\").\n * @property {string} [url] The URL to redirect to.\n */\n\n/**\n * @typedef {Object} PoweredByRenderingOptions\n * @property {Object} widgetParams All original `PoweredByWidgetOptions` forwarded to the `renderFn`.\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 *\n * @type {Connector}\n * @param {function(PoweredByRenderingOptions, boolean)} renderFn Rendering function for the custom **PoweredBy** widget.\n * @param {function} unmountFn Unmount function called when the widget is disposed.\n * @return {function} Re-usable widget factory for a custom **PoweredBy** widget.\n */\nexport default function connectPoweredBy(renderFn, unmountFn = noop) {\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;\n\n return {\n $$type: 'ais.poweredBy',\n\n init() {\n renderFn(\n {\n url,\n widgetParams,\n },\n true\n );\n },\n\n render() {\n renderFn(\n {\n url,\n widgetParams,\n },\n false\n );\n },\n\n dispose() {\n unmountFn();\n },\n };\n };\n}\n","import algoliasearchHelper, {\n SearchParameters,\n PlainSearchParameters,\n AlgoliaSearchHelper,\n} from 'algoliasearch-helper';\nimport {\n Renderer,\n RendererOptions,\n Unmounter,\n WidgetFactory,\n} from '../../types';\nimport {\n createDocumentationMessageGenerator,\n isPlainObject,\n mergeSearchParameters,\n noop,\n} from '../../lib/utils';\n\ntype Refine = (searchParameters: PlainSearchParameters) => void;\n\nexport interface 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 interface ConfigureRendererOptions\n extends RendererOptions {\n refine: Refine;\n}\n\nexport type ConfigureRenderer = Renderer<\n ConfigureRendererOptions\n>;\n\nexport type ConfigureWidgetFactory = WidgetFactory<\n ConfigureConnectorParams & TConfigureWidgetParams\n>;\n\nexport type ConfigureConnector = (\n render?: ConfigureRenderer,\n unmount?: Unmounter\n) => ConfigureWidgetFactory;\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\nconst connectConfigure: ConfigureConnector = (\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 // Trigger a search with the resolved search parameters\n helper.setState(nextSearchParameters).search();\n\n // Update original `widgetParams.searchParameters` to the new refined one\n widgetParams.searchParameters = searchParameters;\n };\n }\n\n return {\n $$type: 'ais.configure',\n\n init({ instantSearchInstance, helper }) {\n connectorState.refine = refine(helper);\n\n renderFn(\n {\n refine: connectorState.refine,\n instantSearchInstance,\n widgetParams,\n },\n true\n );\n },\n\n render({ instantSearchInstance }) {\n renderFn(\n {\n refine: connectorState.refine!,\n instantSearchInstance,\n widgetParams,\n },\n false\n );\n },\n\n dispose({ state }) {\n unmountFn();\n\n return getInitialSearchParameters(state, widgetParams);\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 getWidgetState(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 { Unmounter, WidgetFactory, AlgoliaHit } from '../../types';\nimport {\n createDocumentationMessageGenerator,\n getObjectType,\n warning,\n} from '../../lib/utils';\nimport connectConfigure, {\n ConfigureRenderer,\n ConfigureConnectorParams,\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 interface 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\nexport type ConfigureRelatedItemsWidgetFactory<\n TConfigureRelatedItemsWidgetParams\n> = WidgetFactory<\n ConfigureRelatedItemsConnectorParams & TConfigureRelatedItemsWidgetParams\n>;\n\ntype ConfigureRelatedItemsConnector = (\n render?: ConfigureRenderer,\n unmount?: Unmounter\n) => ConfigureRelatedItemsWidgetFactory;\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\nconst connectConfigureRelatedItems: ConfigureRelatedItemsConnector = (\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 = 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 // @ts-ignore @TODO algoliasearch-helper@3.0.1 will contain the type\n // `sumOrFiltersScores`.\n // See https://github.com/algolia/algoliasearch-helper-js/pull/753\n sumOrFiltersScores: true,\n facetFilters: [`objectID:-${hit.objectID}`],\n // @ts-ignore @TODO algoliasearch-helper@3.0.1 will contain the type\n // `optionalFilters`.\n // See https://github.com/algolia/algoliasearch-helper-js/pull/754\n optionalFilters,\n })\n ),\n };\n\n const makeConfigure = connectConfigure(renderFn, unmountFn);\n\n return {\n ...makeConfigure({ searchParameters }),\n\n $$type: 'ais.configureRelatedItems',\n };\n };\n};\n\nexport default connectConfigureRelatedItems;\n","import { SearchResults } from 'algoliasearch-helper';\nimport escapeHits, { TAG_PLACEHOLDER } from '../../lib/escape-highlight';\nimport {\n checkRendering,\n createDocumentationMessageGenerator,\n noop,\n warning,\n} from '../../lib/utils';\nimport {\n RendererOptions,\n Renderer,\n WidgetFactory,\n Hits,\n InstantSearch,\n Unmounter,\n} from '../../types';\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'autocomplete',\n connector: true,\n});\n\ninterface AutocompleteIndex {\n indexName: string;\n hits: Hits;\n results: SearchResults;\n}\n\ninterface AutocompleteConnectorParams {\n /**\n * Escapes HTML entities from hits string values.\n *\n * @default `true`\n */\n escapeHTML?: boolean;\n}\n\nexport interface AutocompleteRendererOptions\n extends RendererOptions {\n currentRefinement: string;\n indices: AutocompleteIndex[];\n instantSearchInstance: InstantSearch;\n refine: (query: string) => void;\n}\n\nexport type AutocompleteRenderer = Renderer<\n AutocompleteRendererOptions<\n AutocompleteConnectorParams & TAutocompleteWidgetParams\n >\n>;\n\nexport type AutocompleteWidgetFactory<\n TAutocompleteWidgetParams\n> = WidgetFactory;\n\nexport type AutocompleteConnector = (\n render: AutocompleteRenderer,\n unmount?: Unmounter\n) => AutocompleteWidgetFactory;\n\nconst connectAutocomplete: AutocompleteConnector = (\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({ instantSearchInstance, helper }) {\n connectorState.refine = (query: string) => {\n helper.setQuery(query).search();\n };\n\n renderFn(\n {\n widgetParams,\n currentRefinement: helper.state.query || '',\n indices: [],\n refine: connectorState.refine,\n instantSearchInstance,\n },\n true\n );\n },\n\n render({ helper, scopedResults, instantSearchInstance }) {\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 return {\n indexId: scopedResult.indexId,\n indexName: scopedResult.results.index,\n hits: scopedResult.results.hits,\n results: scopedResult.results,\n };\n });\n\n renderFn(\n {\n widgetParams,\n currentRefinement: helper.state.query || '',\n indices,\n refine: connectorState.refine!,\n instantSearchInstance,\n },\n false\n );\n },\n\n getWidgetState(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 {\n Renderer,\n RendererOptions,\n WidgetFactory,\n HelperChangeEvent,\n} 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 interface QueryRulesRendererOptions\n extends RendererOptions {\n items: any[];\n}\n\nexport type QueryRulesRenderer = Renderer<\n QueryRulesRendererOptions\n>;\n\nexport type QueryRulesWidgetFactory = WidgetFactory<\n QueryRulesConnectorParams & TQueryRulesWidgetParams\n>;\n\nexport type QueryRulesConnector = (\n render: QueryRulesRenderer,\n unmount?: () => void\n) => QueryRulesWidgetFactory;\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\nconst connectQueryRules: QueryRulesConnector = (render, unmount = noop) => {\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({ helper, state, instantSearchInstance }) {\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 items: [],\n instantSearchInstance,\n widgetParams,\n },\n true\n );\n },\n\n render({ results, instantSearchInstance }) {\n const { userData = [] } = results;\n const items = transformItems(userData);\n\n render(\n {\n items,\n instantSearchInstance,\n widgetParams,\n },\n false\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","export type VoiceSearchHelperParams = {\n searchAsYouSpeak: boolean;\n language?: string;\n onQueryChange: (query: string) => void;\n onStateChange: () => void;\n};\n\nexport type Status =\n | 'initial'\n | 'askingPermission'\n | 'waiting'\n | 'recognizing'\n | 'finished'\n | 'error';\n\nexport type VoiceListeningState = {\n status: Status;\n transcript: string;\n isSpeechFinal: boolean;\n errorCode?: SpeechRecognitionErrorCode;\n};\n\nexport type VoiceSearchHelper = {\n getState: () => VoiceListeningState;\n isBrowserSupported: () => boolean;\n isListening: () => boolean;\n toggleListening: () => void;\n dispose: () => void;\n};\n\nexport type ToggleListening = () => void;\n\nexport default function createVoiceSearchHelper({\n searchAsYouSpeak,\n language,\n onQueryChange,\n onStateChange,\n}: VoiceSearchHelperParams): VoiceSearchHelper {\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: SpeechRecognitionError): void => {\n setState({ status: 'error', errorCode: event.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 start = (): 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 stop = (): 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 const toggleListening = (): void => {\n if (!isBrowserSupported()) {\n return;\n }\n if (isListening()) {\n stop();\n } else {\n start();\n }\n };\n\n return {\n getState,\n isBrowserSupported,\n isListening,\n toggleListening,\n dispose,\n };\n}\n","import { PlainSearchParameters } from 'algoliasearch-helper';\nimport {\n checkRendering,\n createDocumentationMessageGenerator,\n noop,\n} from '../../lib/utils';\nimport { Renderer, RendererOptions, WidgetFactory } from '../../types';\nimport createVoiceSearchHelper, {\n VoiceListeningState,\n ToggleListening,\n} from '../../lib/voiceSearchHelper';\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};\n\nexport interface VoiceSearchRendererOptions\n extends RendererOptions {\n isBrowserSupported: boolean;\n isListening: boolean;\n toggleListening: ToggleListening;\n voiceListeningState: VoiceListeningState;\n}\n\nexport type VoiceSearchRenderer = Renderer<\n VoiceSearchRendererOptions<\n VoiceSearchConnectorParams & TVoiceSearchWidgetParams\n >\n>;\n\nexport type VoiceSearchWidgetFactory = WidgetFactory<\n VoiceSearchConnectorParams & TVoiceSearchWidgetParams\n>;\n\nexport type VoiceSearchConnector = (\n renderFn: VoiceSearchRenderer,\n unmountFn?: () => void\n) => VoiceSearchWidgetFactory;\n\nconst connectVoiceSearch: VoiceSearchConnector = (\n renderFn,\n unmountFn = noop\n) => {\n checkRendering(renderFn, withUsage());\n\n return widgetParams => {\n const render = ({\n isFirstRendering,\n instantSearchInstance,\n voiceSearchHelper: {\n isBrowserSupported,\n isListening,\n toggleListening,\n getState,\n },\n }): void => {\n renderFn(\n {\n isBrowserSupported: isBrowserSupported(),\n isListening: isListening(),\n toggleListening,\n voiceListeningState: getState(),\n widgetParams,\n instantSearchInstance,\n },\n isFirstRendering\n );\n };\n\n const {\n searchAsYouSpeak = false,\n language,\n additionalQueryParameters,\n } = widgetParams;\n\n return {\n $$type: 'ais.voiceSearch',\n\n init({ helper, instantSearchInstance }) {\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 (this as any)._voiceSearchHelper = createVoiceSearchHelper({\n searchAsYouSpeak,\n language,\n onQueryChange: query => (this as any)._refine(query),\n onStateChange: () => {\n render({\n isFirstRendering: false,\n instantSearchInstance,\n voiceSearchHelper: (this as any)._voiceSearchHelper,\n });\n },\n });\n\n render({\n isFirstRendering: true,\n instantSearchInstance,\n voiceSearchHelper: (this as any)._voiceSearchHelper,\n });\n },\n\n render({ instantSearchInstance }) {\n render({\n isFirstRendering: false,\n instantSearchInstance,\n voiceSearchHelper: (this as any)._voiceSearchHelper,\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 getWidgetState(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","/** @license React v16.9.0\n * react-is.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';Object.defineProperty(exports,\"__esModule\",{value:!0});\nvar b=\"function\"===typeof Symbol&&Symbol.for,c=b?Symbol.for(\"react.element\"):60103,d=b?Symbol.for(\"react.portal\"):60106,e=b?Symbol.for(\"react.fragment\"):60107,f=b?Symbol.for(\"react.strict_mode\"):60108,g=b?Symbol.for(\"react.profiler\"):60114,h=b?Symbol.for(\"react.provider\"):60109,k=b?Symbol.for(\"react.context\"):60110,l=b?Symbol.for(\"react.async_mode\"):60111,m=b?Symbol.for(\"react.concurrent_mode\"):60111,n=b?Symbol.for(\"react.forward_ref\"):60112,p=b?Symbol.for(\"react.suspense\"):60113,q=b?Symbol.for(\"react.suspense_list\"):\n60120,r=b?Symbol.for(\"react.memo\"):60115,t=b?Symbol.for(\"react.lazy\"):60116,v=b?Symbol.for(\"react.fundamental\"):60117,w=b?Symbol.for(\"react.responder\"):60118;function x(a){if(\"object\"===typeof a&&null!==a){var u=a.$$typeof;switch(u){case c:switch(a=a.type,a){case l:case m:case e:case g:case f:case p:return a;default:switch(a=a&&a.$$typeof,a){case k:case n:case h:return a;default:return u}}case t:case r:case d:return u}}}function y(a){return x(a)===m}exports.typeOf=x;exports.AsyncMode=l;\nexports.ConcurrentMode=m;exports.ContextConsumer=k;exports.ContextProvider=h;exports.Element=c;exports.ForwardRef=n;exports.Fragment=e;exports.Lazy=t;exports.Memo=r;exports.Portal=d;exports.Profiler=g;exports.StrictMode=f;exports.Suspense=p;\nexports.isValidElementType=function(a){return\"string\"===typeof a||\"function\"===typeof a||a===e||a===m||a===g||a===f||a===p||a===q||\"object\"===typeof a&&null!==a&&(a.$$typeof===t||a.$$typeof===r||a.$$typeof===h||a.$$typeof===k||a.$$typeof===n||a.$$typeof===v||a.$$typeof===w)};exports.isAsyncMode=function(a){return y(a)||x(a)===l};exports.isConcurrentMode=y;exports.isContextConsumer=function(a){return x(a)===k};exports.isContextProvider=function(a){return x(a)===h};\nexports.isElement=function(a){return\"object\"===typeof a&&null!==a&&a.$$typeof===c};exports.isForwardRef=function(a){return x(a)===n};exports.isFragment=function(a){return x(a)===e};exports.isLazy=function(a){return x(a)===t};exports.isMemo=function(a){return x(a)===r};exports.isPortal=function(a){return x(a)===d};exports.isProfiler=function(a){return x(a)===g};exports.isStrictMode=function(a){return x(a)===f};exports.isSuspense=function(a){return x(a)===p};\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-is.production.min.js');\n} else {\n module.exports = require('./cjs/react-is.development.js');\n}\n","/*\nobject-assign\n(c) Sindre Sorhus\n@license MIT\n*/\n\n'use strict';\n/* eslint-disable no-unused-vars */\nvar getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nvar propIsEnumerable = Object.prototype.propertyIsEnumerable;\n\nfunction toObject(val) {\n\tif (val === null || val === undefined) {\n\t\tthrow new TypeError('Object.assign cannot be called with null or undefined');\n\t}\n\n\treturn Object(val);\n}\n\nfunction shouldUseNative() {\n\ttry {\n\t\tif (!Object.assign) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Detect buggy property enumeration order in older V8 versions.\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=4118\n\t\tvar test1 = new String('abc'); // eslint-disable-line no-new-wrappers\n\t\ttest1[5] = 'de';\n\t\tif (Object.getOwnPropertyNames(test1)[0] === '5') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test2 = {};\n\t\tfor (var i = 0; i < 10; i++) {\n\t\t\ttest2['_' + String.fromCharCode(i)] = i;\n\t\t}\n\t\tvar order2 = Object.getOwnPropertyNames(test2).map(function (n) {\n\t\t\treturn test2[n];\n\t\t});\n\t\tif (order2.join('') !== '0123456789') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test3 = {};\n\t\t'abcdefghijklmnopqrst'.split('').forEach(function (letter) {\n\t\t\ttest3[letter] = letter;\n\t\t});\n\t\tif (Object.keys(Object.assign({}, test3)).join('') !==\n\t\t\t\t'abcdefghijklmnopqrst') {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch (err) {\n\t\t// We don't expect any of the above to throw, but better to be safe.\n\t\treturn false;\n\t}\n}\n\nmodule.exports = shouldUseNative() ? Object.assign : function (target, source) {\n\tvar from;\n\tvar to = toObject(target);\n\tvar symbols;\n\n\tfor (var s = 1; s < arguments.length; s++) {\n\t\tfrom = Object(arguments[s]);\n\n\t\tfor (var key in from) {\n\t\t\tif (hasOwnProperty.call(from, key)) {\n\t\t\t\tto[key] = from[key];\n\t\t\t}\n\t\t}\n\n\t\tif (getOwnPropertySymbols) {\n\t\t\tsymbols = getOwnPropertySymbols(from);\n\t\t\tfor (var i = 0; i < symbols.length; i++) {\n\t\t\t\tif (propIsEnumerable.call(from, symbols[i])) {\n\t\t\t\t\tto[symbols[i]] = from[symbols[i]];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn to;\n};\n","/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';\n\nvar ReactIs = require('react-is');\nvar assign = require('object-assign');\n\nvar ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');\nvar checkPropTypes = require('./checkPropTypes');\n\nvar has = Function.call.bind(Object.prototype.hasOwnProperty);\nvar printWarning = function() {};\n\nif (process.env.NODE_ENV !== 'production') {\n printWarning = function(text) {\n var message = 'Warning: ' + text;\n if (typeof console !== 'undefined') {\n console.error(message);\n }\n try {\n // --- Welcome to debugging React ---\n // This error was thrown as a convenience so that you can use this stack\n // to find the callsite that caused this warning to fire.\n throw new Error(message);\n } catch (x) {}\n };\n}\n\nfunction emptyFunctionThatReturnsNull() {\n return null;\n}\n\nmodule.exports = function(isValidElement, throwOnDirectAccess) {\n /* global Symbol */\n var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;\n var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.\n\n /**\n * Returns the iterator method function contained on the iterable object.\n *\n * Be sure to invoke the function with the iterable as context:\n *\n * var iteratorFn = getIteratorFn(myIterable);\n * if (iteratorFn) {\n * var iterator = iteratorFn.call(myIterable);\n * ...\n * }\n *\n * @param {?object} maybeIterable\n * @return {?function}\n */\n function getIteratorFn(maybeIterable) {\n var iteratorFn = maybeIterable && (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]);\n if (typeof iteratorFn === 'function') {\n return iteratorFn;\n }\n }\n\n /**\n * Collection of methods that allow declaration and validation of props that are\n * supplied to React components. Example usage:\n *\n * var Props = require('ReactPropTypes');\n * var MyArticle = React.createClass({\n * propTypes: {\n * // An optional string prop named \"description\".\n * description: Props.string,\n *\n * // A required enum prop named \"category\".\n * category: Props.oneOf(['News','Photos']).isRequired,\n *\n * // A prop named \"dialog\" that requires an instance of Dialog.\n * dialog: Props.instanceOf(Dialog).isRequired\n * },\n * render: function() { ... }\n * });\n *\n * A more formal specification of how these methods are used:\n *\n * type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...)\n * decl := ReactPropTypes.{type}(.isRequired)?\n *\n * Each and every declaration produces a function with the same signature. This\n * allows the creation of custom validation functions. For example:\n *\n * var MyLink = React.createClass({\n * propTypes: {\n * // An optional string or URI prop named \"href\".\n * href: function(props, propName, componentName) {\n * var propValue = props[propName];\n * if (propValue != null && typeof propValue !== 'string' &&\n * !(propValue instanceof URI)) {\n * return new Error(\n * 'Expected a string or an URI for ' + propName + ' in ' +\n * componentName\n * );\n * }\n * }\n * },\n * render: function() {...}\n * });\n *\n * @internal\n */\n\n var ANONYMOUS = '<>';\n\n // Important!\n // Keep this list in sync with production version in `./factoryWithThrowingShims.js`.\n var ReactPropTypes = {\n array: createPrimitiveTypeChecker('array'),\n bool: createPrimitiveTypeChecker('boolean'),\n func: createPrimitiveTypeChecker('function'),\n number: createPrimitiveTypeChecker('number'),\n object: createPrimitiveTypeChecker('object'),\n string: createPrimitiveTypeChecker('string'),\n symbol: createPrimitiveTypeChecker('symbol'),\n\n any: createAnyTypeChecker(),\n arrayOf: createArrayOfTypeChecker,\n element: createElementTypeChecker(),\n elementType: createElementTypeTypeChecker(),\n instanceOf: createInstanceTypeChecker,\n node: createNodeChecker(),\n objectOf: createObjectOfTypeChecker,\n oneOf: createEnumTypeChecker,\n oneOfType: createUnionTypeChecker,\n shape: createShapeTypeChecker,\n exact: createStrictShapeTypeChecker,\n };\n\n /**\n * inlined Object.is polyfill to avoid requiring consumers ship their own\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is\n */\n /*eslint-disable no-self-compare*/\n function is(x, y) {\n // SameValue algorithm\n if (x === y) {\n // Steps 1-5, 7-10\n // Steps 6.b-6.e: +0 != -0\n return x !== 0 || 1 / x === 1 / y;\n } else {\n // Step 6.a: NaN == NaN\n return x !== x && y !== y;\n }\n }\n /*eslint-enable no-self-compare*/\n\n /**\n * We use an Error-like object for backward compatibility as people may call\n * PropTypes directly and inspect their output. However, we don't use real\n * Errors anymore. We don't inspect their stack anyway, and creating them\n * is prohibitively expensive if they are created too often, such as what\n * happens in oneOfType() for any type before the one that matched.\n */\n function PropTypeError(message) {\n this.message = message;\n this.stack = '';\n }\n // Make `instanceof Error` still work for returned errors.\n PropTypeError.prototype = Error.prototype;\n\n function createChainableTypeChecker(validate) {\n if (process.env.NODE_ENV !== 'production') {\n var manualPropTypeCallCache = {};\n var manualPropTypeWarningCount = 0;\n }\n function checkType(isRequired, props, propName, componentName, location, propFullName, secret) {\n componentName = componentName || ANONYMOUS;\n propFullName = propFullName || propName;\n\n if (secret !== ReactPropTypesSecret) {\n if (throwOnDirectAccess) {\n // New behavior only for users of `prop-types` package\n var err = new Error(\n 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' +\n 'Use `PropTypes.checkPropTypes()` to call them. ' +\n 'Read more at http://fb.me/use-check-prop-types'\n );\n err.name = 'Invariant Violation';\n throw err;\n } else if (process.env.NODE_ENV !== 'production' && typeof console !== 'undefined') {\n // Old behavior for people using React.PropTypes\n var cacheKey = componentName + ':' + propName;\n if (\n !manualPropTypeCallCache[cacheKey] &&\n // Avoid spamming the console because they are often not actionable except for lib authors\n manualPropTypeWarningCount < 3\n ) {\n printWarning(\n 'You are manually calling a React.PropTypes validation ' +\n 'function for the `' + propFullName + '` prop on `' + componentName + '`. This is deprecated ' +\n 'and will throw in the standalone `prop-types` package. ' +\n 'You may be seeing this warning due to a third-party PropTypes ' +\n 'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.'\n );\n manualPropTypeCallCache[cacheKey] = true;\n manualPropTypeWarningCount++;\n }\n }\n }\n if (props[propName] == null) {\n if (isRequired) {\n if (props[propName] === null) {\n return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));\n }\n return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));\n }\n return null;\n } else {\n return validate(props, propName, componentName, location, propFullName);\n }\n }\n\n var chainedCheckType = checkType.bind(null, false);\n chainedCheckType.isRequired = checkType.bind(null, true);\n\n return chainedCheckType;\n }\n\n function createPrimitiveTypeChecker(expectedType) {\n function validate(props, propName, componentName, location, propFullName, secret) {\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== expectedType) {\n // `propValue` being instance of, say, date/regexp, pass the 'object'\n // check, but we can offer a more precise error message here rather than\n // 'of type `object`'.\n var preciseType = getPreciseType(propValue);\n\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') + ('`' + expectedType + '`.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createAnyTypeChecker() {\n return createChainableTypeChecker(emptyFunctionThatReturnsNull);\n }\n\n function createArrayOfTypeChecker(typeChecker) {\n function validate(props, propName, componentName, location, propFullName) {\n if (typeof typeChecker !== 'function') {\n return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.');\n }\n var propValue = props[propName];\n if (!Array.isArray(propValue)) {\n var propType = getPropType(propValue);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.'));\n }\n for (var i = 0; i < propValue.length; i++) {\n var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret);\n if (error instanceof Error) {\n return error;\n }\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createElementTypeChecker() {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n if (!isValidElement(propValue)) {\n var propType = getPropType(propValue);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createElementTypeTypeChecker() {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n if (!ReactIs.isValidElementType(propValue)) {\n var propType = getPropType(propValue);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement type.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createInstanceTypeChecker(expectedClass) {\n function validate(props, propName, componentName, location, propFullName) {\n if (!(props[propName] instanceof expectedClass)) {\n var expectedClassName = expectedClass.name || ANONYMOUS;\n var actualClassName = getClassName(props[propName]);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') + ('instance of `' + expectedClassName + '`.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createEnumTypeChecker(expectedValues) {\n if (!Array.isArray(expectedValues)) {\n if (process.env.NODE_ENV !== 'production') {\n if (arguments.length > 1) {\n printWarning(\n 'Invalid arguments supplied to oneOf, expected an array, got ' + arguments.length + ' arguments. ' +\n 'A common mistake is to write oneOf(x, y, z) instead of oneOf([x, y, z]).'\n );\n } else {\n printWarning('Invalid argument supplied to oneOf, expected an array.');\n }\n }\n return emptyFunctionThatReturnsNull;\n }\n\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n for (var i = 0; i < expectedValues.length; i++) {\n if (is(propValue, expectedValues[i])) {\n return null;\n }\n }\n\n var valuesString = JSON.stringify(expectedValues, function replacer(key, value) {\n var type = getPreciseType(value);\n if (type === 'symbol') {\n return String(value);\n }\n return value;\n });\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of value `' + String(propValue) + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.'));\n }\n return createChainableTypeChecker(validate);\n }\n\n function createObjectOfTypeChecker(typeChecker) {\n function validate(props, propName, componentName, location, propFullName) {\n if (typeof typeChecker !== 'function') {\n return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.');\n }\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== 'object') {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.'));\n }\n for (var key in propValue) {\n if (has(propValue, key)) {\n var error = typeChecker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);\n if (error instanceof Error) {\n return error;\n }\n }\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createUnionTypeChecker(arrayOfTypeCheckers) {\n if (!Array.isArray(arrayOfTypeCheckers)) {\n process.env.NODE_ENV !== 'production' ? printWarning('Invalid argument supplied to oneOfType, expected an instance of array.') : void 0;\n return emptyFunctionThatReturnsNull;\n }\n\n for (var i = 0; i < arrayOfTypeCheckers.length; i++) {\n var checker = arrayOfTypeCheckers[i];\n if (typeof checker !== 'function') {\n printWarning(\n 'Invalid argument supplied to oneOfType. Expected an array of check functions, but ' +\n 'received ' + getPostfixForTypeWarning(checker) + ' at index ' + i + '.'\n );\n return emptyFunctionThatReturnsNull;\n }\n }\n\n function validate(props, propName, componentName, location, propFullName) {\n for (var i = 0; i < arrayOfTypeCheckers.length; i++) {\n var checker = arrayOfTypeCheckers[i];\n if (checker(props, propName, componentName, location, propFullName, ReactPropTypesSecret) == null) {\n return null;\n }\n }\n\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`.'));\n }\n return createChainableTypeChecker(validate);\n }\n\n function createNodeChecker() {\n function validate(props, propName, componentName, location, propFullName) {\n if (!isNode(props[propName])) {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createShapeTypeChecker(shapeTypes) {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== 'object') {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));\n }\n for (var key in shapeTypes) {\n var checker = shapeTypes[key];\n if (!checker) {\n continue;\n }\n var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);\n if (error) {\n return error;\n }\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createStrictShapeTypeChecker(shapeTypes) {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== 'object') {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));\n }\n // We need to check all keys in case some are required but missing from\n // props.\n var allKeys = assign({}, props[propName], shapeTypes);\n for (var key in allKeys) {\n var checker = shapeTypes[key];\n if (!checker) {\n return new PropTypeError(\n 'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`.' +\n '\\nBad object: ' + JSON.stringify(props[propName], null, ' ') +\n '\\nValid keys: ' + JSON.stringify(Object.keys(shapeTypes), null, ' ')\n );\n }\n var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);\n if (error) {\n return error;\n }\n }\n return null;\n }\n\n return createChainableTypeChecker(validate);\n }\n\n function isNode(propValue) {\n switch (typeof propValue) {\n case 'number':\n case 'string':\n case 'undefined':\n return true;\n case 'boolean':\n return !propValue;\n case 'object':\n if (Array.isArray(propValue)) {\n return propValue.every(isNode);\n }\n if (propValue === null || isValidElement(propValue)) {\n return true;\n }\n\n var iteratorFn = getIteratorFn(propValue);\n if (iteratorFn) {\n var iterator = iteratorFn.call(propValue);\n var step;\n if (iteratorFn !== propValue.entries) {\n while (!(step = iterator.next()).done) {\n if (!isNode(step.value)) {\n return false;\n }\n }\n } else {\n // Iterator will provide entry [k,v] tuples rather than values.\n while (!(step = iterator.next()).done) {\n var entry = step.value;\n if (entry) {\n if (!isNode(entry[1])) {\n return false;\n }\n }\n }\n }\n } else {\n return false;\n }\n\n return true;\n default:\n return false;\n }\n }\n\n function isSymbol(propType, propValue) {\n // Native Symbol.\n if (propType === 'symbol') {\n return true;\n }\n\n // falsy value can't be a Symbol\n if (!propValue) {\n return false;\n }\n\n // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol'\n if (propValue['@@toStringTag'] === 'Symbol') {\n return true;\n }\n\n // Fallback for non-spec compliant Symbols which are polyfilled.\n if (typeof Symbol === 'function' && propValue instanceof Symbol) {\n return true;\n }\n\n return false;\n }\n\n // Equivalent of `typeof` but with special handling for array and regexp.\n function getPropType(propValue) {\n var propType = typeof propValue;\n if (Array.isArray(propValue)) {\n return 'array';\n }\n if (propValue instanceof RegExp) {\n // Old webkits (at least until Android 4.0) return 'function' rather than\n // 'object' for typeof a RegExp. We'll normalize this here so that /bla/\n // passes PropTypes.object.\n return 'object';\n }\n if (isSymbol(propType, propValue)) {\n return 'symbol';\n }\n return propType;\n }\n\n // This handles more types than `getPropType`. Only used for error messages.\n // See `createPrimitiveTypeChecker`.\n function getPreciseType(propValue) {\n if (typeof propValue === 'undefined' || propValue === null) {\n return '' + propValue;\n }\n var propType = getPropType(propValue);\n if (propType === 'object') {\n if (propValue instanceof Date) {\n return 'date';\n } else if (propValue instanceof RegExp) {\n return 'regexp';\n }\n }\n return propType;\n }\n\n // Returns a string that is postfixed to a warning about an invalid type.\n // For example, \"undefined\" or \"of type array\"\n function getPostfixForTypeWarning(value) {\n var type = getPreciseType(value);\n switch (type) {\n case 'array':\n case 'object':\n return 'an ' + type;\n case 'boolean':\n case 'date':\n case 'regexp':\n return 'a ' + type;\n default:\n return type;\n }\n }\n\n // Returns class name of the object, if any.\n function getClassName(propValue) {\n if (!propValue.constructor || !propValue.constructor.name) {\n return ANONYMOUS;\n }\n return propValue.constructor.name;\n }\n\n ReactPropTypes.checkPropTypes = checkPropTypes;\n ReactPropTypes.resetWarningCache = checkPropTypes.resetWarningCache;\n ReactPropTypes.PropTypes = ReactPropTypes;\n\n return ReactPropTypes;\n};\n","/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';\n\nvar ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');\n\nfunction emptyFunction() {}\nfunction emptyFunctionWithReset() {}\nemptyFunctionWithReset.resetWarningCache = emptyFunction;\n\nmodule.exports = function() {\n function shim(props, propName, componentName, location, propFullName, secret) {\n if (secret === ReactPropTypesSecret) {\n // It is still safe when called from React.\n return;\n }\n var err = new Error(\n 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' +\n 'Use PropTypes.checkPropTypes() to call them. ' +\n 'Read more at http://fb.me/use-check-prop-types'\n );\n err.name = 'Invariant Violation';\n throw err;\n };\n shim.isRequired = shim;\n function getShim() {\n return shim;\n };\n // Important!\n // Keep this list in sync with production version in `./factoryWithTypeCheckers.js`.\n var ReactPropTypes = {\n array: shim,\n bool: shim,\n func: shim,\n number: shim,\n object: shim,\n string: shim,\n symbol: shim,\n\n any: shim,\n arrayOf: getShim,\n element: shim,\n elementType: shim,\n instanceOf: getShim,\n node: shim,\n objectOf: getShim,\n oneOf: getShim,\n oneOfType: getShim,\n shape: getShim,\n exact: getShim,\n\n checkPropTypes: emptyFunctionWithReset,\n resetWarningCache: emptyFunction\n };\n\n ReactPropTypes.PropTypes = ReactPropTypes;\n\n return ReactPropTypes;\n};\n","/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nif (process.env.NODE_ENV !== 'production') {\n var ReactIs = require('react-is');\n\n // By explicitly using `prop-types` you are opting into new development behavior.\n // http://fb.me/prop-types-in-prod\n var throwOnDirectAccess = true;\n module.exports = require('./factoryWithTypeCheckers')(ReactIs.isElement, throwOnDirectAccess);\n} else {\n // By explicitly using `prop-types` you are opting into new production behavior.\n // http://fb.me/prop-types-in-prod\n module.exports = require('./factoryWithThrowingShims')();\n}\n","/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';\n\nvar ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';\n\nmodule.exports = ReactPropTypesSecret;\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 });\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};\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","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 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';\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\n/**\n * @typedef {Object} ClearRefinementsCSSClasses\n * @property {string|string[]} [root] CSS class to add to the wrapper element.\n * @property {string|string[]} [button] CSS class to add to the button of the widget.\n * @property {string|string[]} [disabledButton] CSS class to add to the button when there are no refinements.\n */\n\n/**\n * @typedef {Object} ClearRefinementsTemplates\n * @property {string|string[]} [resetLabel] Template for the content of the button\n */\n\n/**\n * @typedef {Object} ClearRefinementsWidgetOptions\n * @property {string|HTMLElement} container CSS Selector or HTMLElement to insert the widget.\n * @property {string[]} [includedAttributes = []] The attributes to include in the refinements to clear (all by default). Cannot be used with `excludedAttributes`.\n * @property {string[]} [excludedAttributes = ['query']] The attributes to exclude from the refinements to clear. Cannot be used with `includedAttributes`.\n * @property {function(object[]):object[]} [transformItems] Function to transform the items passed to the templates.\n * @property {ClearRefinementsTemplates} [templates] Templates to use for the widget.\n * @property {ClearRefinementsCSSClasses} [cssClasses] CSS classes to be added.\n */\n\n/**\n * The clear all widget gives the user the ability to clear all the refinements currently\n * applied on the results. It is equivalent to the reset button of a form.\n *\n * The current refined values widget can display a button that has the same behavior.\n * @type {WidgetFactory}\n * @devNovel ClearRefinements\n * @category clear-filter\n * @param {ClearRefinementsWidgetOptions} $0 The ClearRefinements widget options.\n * @returns {Widget} A new instance of the ClearRefinements widget.\n * @example\n * search.addWidgets([\n * instantsearch.widgets.clearRefinements({\n * container: '#clear-all',\n * templates: {\n * resetLabel: 'Reset everything'\n * },\n * })\n * ]);\n */\nexport default function clearRefinements({\n container,\n templates = defaultTemplates,\n includedAttributes,\n excludedAttributes,\n transformItems,\n cssClasses: userCssClasses = {},\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 makeWidget({\n includedAttributes,\n excludedAttributes,\n transformItems,\n });\n}\n","/** @jsx h */\n\nimport { h } from 'preact';\nimport { isSpecialClick, capitalize } from '../../lib/utils';\nimport { Item } from '../../connectors/current-refinements/connectCurrentRefinements';\nimport { CurrentRefinementsComponentCSSClasses } from '../../widgets/current-refinements/current-refinements';\n\ntype CurrentRefinementsProps = {\n items: Item[];\n cssClasses: CurrentRefinementsComponentCSSClasses;\n};\n\nconst createItemKey = ({\n attribute,\n value,\n type,\n operator,\n}: {\n attribute: string;\n value: string;\n type: string;\n operator?: string;\n}): 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 CurrentRefinementsRenderer,\n Item,\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\ntype CurrentRefinementsCSSClasses = {\n root: string | string[];\n list: string | string[];\n item: string | string[];\n label: string | string[];\n category: string | string[];\n categoryLabel: string | string[];\n delete: string | string[];\n};\n\nexport type CurrentRefinementsComponentCSSClasses = {\n [TClassName in keyof CurrentRefinementsCSSClasses]: 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 * The CSS classes to override.\n */\n cssClasses?: Partial;\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 * The attributes to exclude from the widget.\n * Cannot be used with `includedAttributes`.\n *\n * @default `['query']`\n */\n excludedAttributes?: string[];\n /**\n * Receives the items, and is called before displaying them.\n * Should return a new array with the same shape as the original array.\n * Useful for mapping over the items to transform, and remove or reorder them.\n */\n transformItems?: (items: Item[]) => any;\n};\n\ninterface CurrentRefinementsRendererWidgetParams\n extends CurrentRefinementsWidgetParams {\n container: HTMLElement;\n cssClasses: CurrentRefinementsComponentCSSClasses;\n}\n\ntype CurrentRefinements = WidgetFactory;\n\nconst withUsage = createDocumentationMessageGenerator({\n name: 'current-refinements',\n});\nconst suit = component('CurrentRefinements');\n\nconst renderer: CurrentRefinementsRenderer = (\n { items, widgetParams },\n isFirstRender\n) => {\n if (isFirstRender) {\n return;\n }\n\n const { container, cssClasses } = widgetParams;\n\n render(\n ,\n container\n );\n};\n\nconst currentRefinements: CurrentRefinements = ({\n container,\n includedAttributes,\n excludedAttributes,\n cssClasses: userCssClasses = {} as CurrentRefinementsCSSClasses,\n transformItems,\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(renderer, () =>\n render(null, containerNode)\n );\n\n return makeWidget({\n container: containerNode,\n cssClasses,\n includedAttributes,\n excludedAttributes,\n transformItems,\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","import { PlainSearchParameters } from 'algoliasearch-helper';\nimport connectConfigure from '../../connectors/configure/connectConfigure';\nimport { WidgetFactory } from '../../types';\n\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 */\ntype ConfigureWidgetParams = PlainSearchParameters;\n\ntype Configure = WidgetFactory;\n\nconst configure: Configure = (widgetParams: ConfigureWidgetParams) => {\n // This is a renderless widget that falls back to the connector's\n // noop render and unmount functions.\n const makeWidget = connectConfigure();\n\n return makeWidget({ searchParameters: widgetParams });\n};\n\nexport default configure;\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