instantsearch.js 4.32.0 → 4.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +24 -1
  3. package/cjs/connectors/autocomplete/connectAutocomplete.js +2 -1
  4. package/cjs/connectors/infinite-hits/connectInfiniteHits.js +1 -1
  5. package/cjs/connectors/numeric-menu/connectNumericMenu.js +6 -4
  6. package/cjs/connectors/pagination/connectPagination.js +3 -3
  7. package/cjs/connectors/powered-by/connectPoweredBy.js +13 -4
  8. package/cjs/connectors/search-box/connectSearchBox.js +4 -3
  9. package/cjs/connectors/sort-by/connectSortBy.js +2 -1
  10. package/cjs/connectors/stats/connectStats.js +4 -4
  11. package/cjs/connectors/toggle-refinement/connectToggleRefinement.js +1 -1
  12. package/cjs/lib/InstantSearch.js +26 -3
  13. package/cjs/lib/infiniteHitsCache/sessionStorage.js +16 -12
  14. package/cjs/lib/routers/history.js +89 -42
  15. package/cjs/lib/utils/detect-insights-client.js +10 -1
  16. package/cjs/lib/utils/index.js +10 -1
  17. package/cjs/lib/utils/safelyRunOnBrowser.js +30 -0
  18. package/cjs/lib/version.js +1 -1
  19. package/cjs/lib/voiceSearchHelper/index.js +5 -0
  20. package/cjs/middlewares/createMetadataMiddleware.js +12 -3
  21. package/cjs/widgets/index/index.js +21 -3
  22. package/dist/instantsearch.development.d.ts +21 -2
  23. package/dist/instantsearch.development.js +227 -88
  24. package/dist/instantsearch.development.js.map +1 -1
  25. package/dist/instantsearch.development.min.d.ts +21 -2
  26. package/dist/instantsearch.production.d.ts +21 -2
  27. package/dist/instantsearch.production.min.d.ts +21 -2
  28. package/dist/instantsearch.production.min.js +2 -2
  29. package/dist/instantsearch.production.min.js.map +1 -1
  30. package/es/connectors/autocomplete/connectAutocomplete.js +2 -1
  31. package/es/connectors/infinite-hits/connectInfiniteHits.js +1 -1
  32. package/es/connectors/numeric-menu/connectNumericMenu.js +6 -4
  33. package/es/connectors/pagination/connectPagination.js +3 -3
  34. package/es/connectors/powered-by/connectPoweredBy.js +14 -5
  35. package/es/connectors/search-box/connectSearchBox.js +4 -3
  36. package/es/connectors/sort-by/connectSortBy.js +2 -1
  37. package/es/connectors/stats/connectStats.js +4 -4
  38. package/es/connectors/toggle-refinement/connectToggleRefinement.js +1 -1
  39. package/es/lib/InstantSearch.d.ts +2 -1
  40. package/es/lib/InstantSearch.js +26 -3
  41. package/es/lib/infiniteHitsCache/sessionStorage.js +17 -14
  42. package/es/lib/routers/history.d.ts +13 -2
  43. package/es/lib/routers/history.js +88 -42
  44. package/es/lib/utils/detect-insights-client.js +9 -1
  45. package/es/lib/utils/index.d.ts +1 -0
  46. package/es/lib/utils/index.js +2 -1
  47. package/es/lib/utils/safelyRunOnBrowser.d.ts +14 -0
  48. package/es/lib/utils/safelyRunOnBrowser.js +23 -0
  49. package/es/lib/version.d.ts +1 -1
  50. package/es/lib/version.js +1 -1
  51. package/es/lib/voiceSearchHelper/index.js +5 -0
  52. package/es/middlewares/createMetadataMiddleware.js +12 -3
  53. package/es/types/results.d.ts +7 -0
  54. package/es/widgets/index/index.js +21 -3
  55. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,3 +1,46 @@
1
+ # [4.34.0](https://github.com/algolia/instantsearch.js/compare/v4.33.2...v4.34.0) (2021-12-07)
2
+
3
+
4
+ ### Features
5
+
6
+ * rely on `state` in `getWidgetRenderState` ([#4960](https://github.com/algolia/instantsearch.js/issues/4960)) ([5006841](https://github.com/algolia/instantsearch.js/commit/50068417e5e7211802bc717b582946f6e630d7ac))
7
+ * support initial results (experimental) ([#4967](https://github.com/algolia/instantsearch.js/issues/4967)) ([db11c13](https://github.com/algolia/instantsearch.js/commit/db11c13ea55433491f5e924633bff12a303c1bc6))
8
+
9
+
10
+
11
+ ## [4.33.2](https://github.com/algolia/instantsearch.js/compare/v4.33.1...v4.33.2) (2021-11-16)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **connectNumericMenu:** allow option for same start/end values ([#4951](https://github.com/algolia/instantsearch.js/issues/4951)) ([18da714](https://github.com/algolia/instantsearch.js/commit/18da714574fa98957d29014add3123e9c377551f))
17
+
18
+
19
+
20
+ ## [4.33.1](https://github.com/algolia/instantsearch.js/compare/v4.33.0...v4.33.1) (2021-11-02)
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * **getUiState:** support `initialUiState` ([#4948](https://github.com/algolia/instantsearch.js/issues/4948)) ([532474d](https://github.com/algolia/instantsearch.js/commit/532474dfaf49446ab59a2a27424ca220947dd5bd))
26
+
27
+
28
+
29
+ # [4.33.0](https://github.com/algolia/instantsearch.js/compare/v4.32.0...v4.33.0) (2021-10-26)
30
+
31
+
32
+ ### Bug Fixes
33
+
34
+ * **router:** skip history push on browser back and forward actions ([#4933](https://github.com/algolia/instantsearch.js/issues/4933)) ([7909da4](https://github.com/algolia/instantsearch.js/commit/7909da4903eb1aee885811e280b909a3bda488be))
35
+ * **setUiState:** reset UI state with empty object ([#4944](https://github.com/algolia/instantsearch.js/issues/4944)) ([5faae4a](https://github.com/algolia/instantsearch.js/commit/5faae4ac44ac5ad2f8086ad2a306bcfaa14bc754))
36
+
37
+
38
+ ### Features
39
+
40
+ * **router:** support server environments ([#4940](https://github.com/algolia/instantsearch.js/issues/4940)) ([a002962](https://github.com/algolia/instantsearch.js/commit/a002962df0e7683b29bef8bfaaddb494fa551a14))
41
+
42
+
43
+
1
44
  # [4.32.0](https://github.com/algolia/instantsearch.js/compare/v4.31.1...v4.32.0) (2021-10-20)
2
45
 
3
46
 
package/README.md CHANGED
@@ -29,6 +29,7 @@ InstantSearch.js is a vanilla JavaScript library that lets you create an instant
29
29
  - [Why](#why)
30
30
  - [Getting started](#getting-started)
31
31
  - [Installation](#installation)
32
+ - [TypeScript users](#typescript-users)
32
33
  - [Documentation](#documentation)
33
34
  - [Demos](#demos)
34
35
  - [Playground](#playground)
@@ -102,6 +103,28 @@ npm install instantsearch.js algoliasearch
102
103
  yarn add instantsearch.js algoliasearch
103
104
  ```
104
105
 
106
+ ### TypeScript users
107
+
108
+ To use InstantSearch.js in a TypeScript environment, depending on your [`algoliasearch`](https://github.com/algolia/algoliasearch-client-javascript) version, you need to import different types.
109
+
110
+ >You still need to import these types even if you don't use InstantSearch.js with [`algoliasearch`](https://github.com/algolia/algoliasearch-client-javascript).
111
+
112
+ #### `algoliasearch` v4.x
113
+
114
+ This version uses types provided by both `algoliasearch` and `@algolia/client-search`.
115
+
116
+ ```bash
117
+ yarn add algoliasearch@4 @algolia/client-search
118
+ ```
119
+
120
+ #### `algoliasearch` v3.x
121
+
122
+ ```bash
123
+ yarn add @types/algoliasearch@3
124
+ ```
125
+
126
+ >v3.x is deprecated and will soon no longer be supported.
127
+
105
128
  ## Documentation
106
129
 
107
130
  The documentation is available on the [Algolia website](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js/).
@@ -110,7 +133,7 @@ The documentation is available on the [Algolia website](https://www.algolia.com/
110
133
 
111
134
  | E-commerce | Media | Travel |
112
135
  | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
113
- | <a href="https://instantsearchjs.netlify.com/examples/e-commerce/"><img src="https://www.algolia.com/doc/assets/images/build-search-ui/demos/e-commerce-2c7ed6b6.png" width="250" alt="E-commerce demo preview"></a> | <a href="https://instantsearchjs.netlify.com/examples/media/"><img src="https://instantsearchjs.netlify.com/examples/media/capture.png" width="250" alt="Media demo preview"></a> | <a href="https://instantsearchjs.netlify.com/examples/tourism/"><img src="https://instantsearchjs.netlify.com/examples/tourism/capture.png" width="250" alt="Tourism demo preview"></a> |
136
+ | <a href="https://instantsearchjs.netlify.com/examples/e-commerce/"><img src="https://www.algolia.com/doc/assets/images/build-search-ui/demos/e-commerce-2c7ed6b6.png" width="250" alt="E-commerce demo preview"></a> | <a href="https://instantsearchjs.netlify.com/examples/media/"><img src="./examples/media/capture.png" width="250" alt="Media demo preview"></a> | <a href="https://instantsearchjs.netlify.com/examples/tourism/"><img src="https://instantsearchjs.netlify.com/examples/tourism/capture.png" width="250" alt="Tourism demo preview"></a> |
114
137
 
115
138
  See more demos [on the website](https://www.algolia.com/doc/guides/building-search-ui/resources/demos/js/).
116
139
 
@@ -60,6 +60,7 @@ var connectAutocomplete = function connectAutocomplete(renderFn) {
60
60
  var _this = this;
61
61
 
62
62
  var helper = _ref4.helper,
63
+ state = _ref4.state,
63
64
  scopedResults = _ref4.scopedResults,
64
65
  instantSearchInstance = _ref4.instantSearchInstance;
65
66
 
@@ -87,7 +88,7 @@ var connectAutocomplete = function connectAutocomplete(renderFn) {
87
88
  };
88
89
  });
89
90
  return {
90
- currentRefinement: helper.state.query || '',
91
+ currentRefinement: state.query || '',
91
92
  indices: indices,
92
93
  refine: connectorState.refine,
93
94
  widgetParams: widgetParams
@@ -173,7 +173,7 @@ var connectInfiniteHits = function connectInfiniteHits(renderFn) {
173
173
  index: helper.getIndex(),
174
174
  widgetType: this.$$type
175
175
  });
176
- isFirstPage = helper.state.page === undefined || getFirstReceivedPage(helper.state, cachedHits) === 0;
176
+ isFirstPage = state.page === undefined || getFirstReceivedPage(state, cachedHits) === 0;
177
177
  } else {
178
178
  var _state$page3 = state.page,
179
179
  _page = _state$page3 === void 0 ? 0 : _state$page3;
@@ -242,6 +242,8 @@ function isRefined(state, attribute, option) {
242
242
  if (option.start !== undefined && option.end !== undefined) {
243
243
  if (option.start === option.end) {
244
244
  return hasNumericRefinement(currentRefinements, '=', option.start);
245
+ } else {
246
+ return hasNumericRefinement(currentRefinements, '>=', option.start) && hasNumericRefinement(currentRefinements, '<=', option.end);
245
247
  }
246
248
  }
247
249
 
@@ -295,17 +297,17 @@ function getRefinedState(state, attribute, facetValue) {
295
297
  if (refinedOption.start !== undefined) {
296
298
  if (hasNumericRefinement(currentRefinements, '>=', refinedOption.start)) {
297
299
  resolvedState = resolvedState.removeNumericRefinement(attribute, '>=', refinedOption.start);
298
- } else {
299
- resolvedState = resolvedState.addNumericRefinement(attribute, '>=', refinedOption.start);
300
300
  }
301
+
302
+ resolvedState = resolvedState.addNumericRefinement(attribute, '>=', refinedOption.start);
301
303
  }
302
304
 
303
305
  if (refinedOption.end !== undefined) {
304
306
  if (hasNumericRefinement(currentRefinements, '<=', refinedOption.end)) {
305
307
  resolvedState = resolvedState.removeNumericRefinement(attribute, '<=', refinedOption.end);
306
- } else {
307
- resolvedState = resolvedState.addNumericRefinement(attribute, '<=', refinedOption.end);
308
308
  }
309
+
310
+ resolvedState = resolvedState.addNumericRefinement(attribute, '<=', refinedOption.end);
309
311
  }
310
312
 
311
313
  if (typeof resolvedState.page === 'number') {
@@ -89,6 +89,7 @@ var connectPagination = function connectPagination(renderFn) {
89
89
  getWidgetRenderState: function getWidgetRenderState(_ref6) {
90
90
  var results = _ref6.results,
91
91
  helper = _ref6.helper,
92
+ state = _ref6.state,
92
93
  createURL = _ref6.createURL;
93
94
 
94
95
  if (!connectorState.refine) {
@@ -99,14 +100,13 @@ var connectPagination = function connectPagination(renderFn) {
99
100
  }
100
101
 
101
102
  if (!connectorState.createURL) {
102
- connectorState.createURL = function (state) {
103
+ connectorState.createURL = function (helperState) {
103
104
  return function (page) {
104
- return createURL(state.setPage(page));
105
+ return createURL(helperState.setPage(page));
105
106
  };
106
107
  };
107
108
  }
108
109
 
109
- var state = helper.state;
110
110
  var page = state.page || 0;
111
111
  var nbPages = getMaxPage(results || {
112
112
  nbPages: 0
@@ -25,11 +25,20 @@ var withUsage = (0, _utils.createDocumentationMessageGenerator)({
25
25
  var connectPoweredBy = function connectPoweredBy(renderFn) {
26
26
  var unmountFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _utils.noop;
27
27
  (0, _utils.checkRendering)(renderFn, withUsage());
28
- var defaultUrl = 'https://www.algolia.com/?' + 'utm_source=instantsearch.js&' + 'utm_medium=website&' + "utm_content=".concat(typeof window !== 'undefined' && window.location ? window.location.hostname : '', "&") + 'utm_campaign=poweredby';
28
+ var defaultUrl = 'https://www.algolia.com/?' + 'utm_source=instantsearch.js&' + 'utm_medium=website&' + "utm_content=".concat((0, _utils.safelyRunOnBrowser)(function (_ref) {
29
+ var _window$location;
30
+
31
+ var window = _ref.window;
32
+ return ((_window$location = window.location) === null || _window$location === void 0 ? void 0 : _window$location.hostname) || '';
33
+ }, {
34
+ fallback: function fallback() {
35
+ return '';
36
+ }
37
+ }), "&") + 'utm_campaign=poweredby';
29
38
  return function (widgetParams) {
30
- var _ref = widgetParams || {},
31
- _ref$url = _ref.url,
32
- url = _ref$url === void 0 ? defaultUrl : _ref$url;
39
+ var _ref2 = widgetParams || {},
40
+ _ref2$url = _ref2.url,
41
+ url = _ref2$url === void 0 ? defaultUrl : _ref2$url;
33
42
 
34
43
  return {
35
44
  $$type: 'ais.poweredBy',
@@ -71,11 +71,12 @@ var connectSearchBox = function connectSearchBox(renderFn) {
71
71
  },
72
72
  getWidgetRenderState: function getWidgetRenderState(_ref3) {
73
73
  var helper = _ref3.helper,
74
- searchMetadata = _ref3.searchMetadata;
74
+ searchMetadata = _ref3.searchMetadata,
75
+ state = _ref3.state;
75
76
 
76
77
  if (!_refine) {
77
78
  var setQueryAndSearch = function setQueryAndSearch(query) {
78
- if (query !== helper.state.query) {
79
+ if (query !== state.query) {
79
80
  helper.setQuery(query).search();
80
81
  }
81
82
  };
@@ -92,7 +93,7 @@ var connectSearchBox = function connectSearchBox(renderFn) {
92
93
 
93
94
  _clear = clear(helper);
94
95
  return {
95
- query: helper.state.query || '',
96
+ query: state.query || '',
96
97
  refine: _refine,
97
98
  clear: _cachedClear,
98
99
  widgetParams: widgetParams,
@@ -72,6 +72,7 @@ var connectSortBy = function connectSortBy(renderFn) {
72
72
  getWidgetRenderState: function getWidgetRenderState(_ref3) {
73
73
  var results = _ref3.results,
74
74
  helper = _ref3.helper,
75
+ state = _ref3.state,
75
76
  parent = _ref3.parent;
76
77
 
77
78
  if (!connectorState.initialIndex && parent) {
@@ -85,7 +86,7 @@ var connectSortBy = function connectSortBy(renderFn) {
85
86
  }
86
87
 
87
88
  return {
88
- currentRefinement: helper.state.index,
89
+ currentRefinement: state.index,
89
90
  options: transformItems(items),
90
91
  refine: connectorState.setIndex,
91
92
  hasNoResults: results ? results.nbHits === 0 : true,
@@ -50,18 +50,18 @@ var connectStats = function connectStats(renderFn) {
50
50
  },
51
51
  getWidgetRenderState: function getWidgetRenderState(_ref) {
52
52
  var results = _ref.results,
53
- helper = _ref.helper;
53
+ state = _ref.state;
54
54
 
55
55
  if (!results) {
56
56
  return {
57
- hitsPerPage: helper.state.hitsPerPage,
57
+ hitsPerPage: state.hitsPerPage,
58
58
  nbHits: 0,
59
59
  nbSortedHits: undefined,
60
60
  areHitsSorted: false,
61
61
  nbPages: 0,
62
- page: helper.state.page || 0,
62
+ page: state.page || 0,
63
63
  processingTimeMS: -1,
64
- query: helper.state.query || '',
64
+ query: state.query || '',
65
65
  widgetParams: widgetParams
66
66
  };
67
67
  }
@@ -185,7 +185,7 @@ var connectToggleRefinement = function connectToggleRefinement(renderFn) {
185
185
  createURL = _ref6.createURL,
186
186
  instantSearchInstance = _ref6.instantSearchInstance;
187
187
  var isRefined = results ? on.every(function (v) {
188
- return helper.state.isDisjunctiveFacetRefined(attribute, v);
188
+ return state.isDisjunctiveFacetRefined(attribute, v);
189
189
  }) : on.every(function (v) {
190
190
  return state.isDisjunctiveFacetRefined(attribute, v);
191
191
  });
@@ -112,6 +112,8 @@ var InstantSearch = /*#__PURE__*/function (_EventEmitter) {
112
112
 
113
113
  _defineProperty(_assertThisInitialized(_this), "_initialUiState", void 0);
114
114
 
115
+ _defineProperty(_assertThisInitialized(_this), "_initialResults", void 0);
116
+
115
117
  _defineProperty(_assertThisInitialized(_this), "_createURL", void 0);
116
118
 
117
119
  _defineProperty(_assertThisInitialized(_this), "_searchFunction", void 0);
@@ -216,6 +218,7 @@ var InstantSearch = /*#__PURE__*/function (_EventEmitter) {
216
218
  _this._isSearchStalled = false;
217
219
  _this._createURL = defaultCreateURL;
218
220
  _this._initialUiState = initialUiState;
221
+ _this._initialResults = null;
219
222
 
220
223
  if (searchFunction) {
221
224
  _this._searchFunction = searchFunction;
@@ -458,9 +461,27 @@ var InstantSearch = /*#__PURE__*/function (_EventEmitter) {
458
461
  parent: null,
459
462
  uiState: this._initialUiState
460
463
  });
461
- this.scheduleSearch(); // Keep the previous reference for legacy purpose, some pattern use
464
+
465
+ if (this._initialResults) {
466
+ var originalScheduleSearch = this.scheduleSearch; // We don't schedule a first search when initial results are provided
467
+ // because we already have the results to render. This skips the initial
468
+ // network request on the browser on `start`.
469
+
470
+ this.scheduleSearch = (0, _utils.defer)(_utils.noop); // We also skip the initial network request when widgets are dynamically
471
+ // added in the first tick (that's the case in all the framework-based flavors).
472
+ // When we add a widget to `index`, it calls `scheduleSearch`. We can rely
473
+ // on our `defer` util to restore the original `scheduleSearch` value once
474
+ // widgets are added to hook back to the regular lifecycle.
475
+
476
+ (0, _utils.defer)(function () {
477
+ _this3.scheduleSearch = originalScheduleSearch;
478
+ })();
479
+ } else {
480
+ this.scheduleSearch();
481
+ } // Keep the previous reference for legacy purpose, some pattern use
462
482
  // the direct Helper access `search.helper` (e.g multi-index).
463
483
 
484
+
464
485
  this.helper = this.mainIndex.getHelper(); // track we started the search if we add more widgets,
465
486
  // to init them directly after add
466
487
 
@@ -522,15 +543,17 @@ var InstantSearch = /*#__PURE__*/function (_EventEmitter) {
522
543
  var nextUiState = typeof uiState === 'function' ? uiState(this.mainIndex.getWidgetUiState({})) : uiState;
523
544
 
524
545
  var setIndexHelperState = function setIndexHelperState(indexWidget) {
546
+ var nextIndexUiState = nextUiState[indexWidget.getIndexId()] || {};
547
+
525
548
  if (process.env.NODE_ENV === 'development') {
526
549
  (0, _utils.checkIndexUiState)({
527
550
  index: indexWidget,
528
- indexUiState: nextUiState[indexWidget.getIndexId()]
551
+ indexUiState: nextIndexUiState
529
552
  });
530
553
  }
531
554
 
532
555
  indexWidget.getHelper().setState(indexWidget.getWidgetSearchParameters(indexWidget.getHelper().state, {
533
- uiState: nextUiState[indexWidget.getIndexId()]
556
+ uiState: nextIndexUiState
534
557
  }));
535
558
  indexWidget.getWidgets().filter(_index.isIndexWidget).forEach(setIndexHelperState);
536
559
  };
@@ -21,27 +21,27 @@ function getStateWithoutPage(state) {
21
21
 
22
22
  var KEY = 'ais.infiniteHits';
23
23
 
24
- function hasSessionStorage() {
25
- return typeof window !== 'undefined' && typeof window.sessionStorage !== 'undefined';
26
- }
27
-
28
24
  function createInfiniteHitsSessionStorageCache() {
29
25
  return {
30
26
  read: function read(_ref2) {
31
27
  var state = _ref2.state;
28
+ var sessionStorage = (0, _utils.safelyRunOnBrowser)(function (_ref3) {
29
+ var window = _ref3.window;
30
+ return window.sessionStorage;
31
+ });
32
32
 
33
- if (!hasSessionStorage()) {
33
+ if (!sessionStorage) {
34
34
  return null;
35
35
  }
36
36
 
37
37
  try {
38
38
  var cache = JSON.parse( // @ts-expect-error JSON.parse() requires a string, but it actually accepts null, too.
39
- window.sessionStorage.getItem(KEY));
39
+ sessionStorage.getItem(KEY));
40
40
  return cache && (0, _utils.isEqual)(cache.state, getStateWithoutPage(state)) ? cache.hits : null;
41
41
  } catch (error) {
42
42
  if (error instanceof SyntaxError) {
43
43
  try {
44
- window.sessionStorage.removeItem(KEY);
44
+ sessionStorage.removeItem(KEY);
45
45
  } catch (err) {// do nothing
46
46
  }
47
47
  }
@@ -49,16 +49,20 @@ function createInfiniteHitsSessionStorageCache() {
49
49
  return null;
50
50
  }
51
51
  },
52
- write: function write(_ref3) {
53
- var state = _ref3.state,
54
- hits = _ref3.hits;
52
+ write: function write(_ref4) {
53
+ var state = _ref4.state,
54
+ hits = _ref4.hits;
55
+ var sessionStorage = (0, _utils.safelyRunOnBrowser)(function (_ref5) {
56
+ var window = _ref5.window;
57
+ return window.sessionStorage;
58
+ });
55
59
 
56
- if (!hasSessionStorage()) {
60
+ if (!sessionStorage) {
57
61
  return;
58
62
  }
59
63
 
60
64
  try {
61
- window.sessionStorage.setItem(KEY, JSON.stringify({
65
+ sessionStorage.setItem(KEY, JSON.stringify({
62
66
  state: getStateWithoutPage(state),
63
67
  hits: hits
64
68
  }));
@@ -7,6 +7,8 @@ exports.default = historyRouter;
7
7
 
8
8
  var _qs = _interopRequireDefault(require("qs"));
9
9
 
10
+ var _utils = require("../utils");
11
+
10
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
13
 
12
14
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
@@ -19,6 +21,8 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
19
21
 
20
22
  var setWindowTitle = function setWindowTitle(title) {
21
23
  if (title) {
24
+ // This function is only executed on browsers so we can disable this check.
25
+ // eslint-disable-next-line no-restricted-globals
22
26
  window.document.title = title;
23
27
  }
24
28
  };
@@ -29,11 +33,14 @@ var BrowserHistory = /*#__PURE__*/function () {
29
33
  * using web APIs (`window.location.pushState` and `onpopstate` event).
30
34
  */
31
35
  function BrowserHistory(_ref) {
36
+ var _this = this;
37
+
32
38
  var windowTitle = _ref.windowTitle,
33
39
  _ref$writeDelay = _ref.writeDelay,
34
40
  writeDelay = _ref$writeDelay === void 0 ? 400 : _ref$writeDelay,
35
41
  createURL = _ref.createURL,
36
- parseURL = _ref.parseURL;
42
+ parseURL = _ref.parseURL,
43
+ getLocation = _ref.getLocation;
37
44
 
38
45
  _classCallCheck(this, BrowserHistory);
39
46
 
@@ -45,15 +52,23 @@ var BrowserHistory = /*#__PURE__*/function () {
45
52
 
46
53
  _defineProperty(this, "parseURL", void 0);
47
54
 
55
+ _defineProperty(this, "getLocation", void 0);
56
+
48
57
  _defineProperty(this, "writeTimer", void 0);
49
58
 
59
+ _defineProperty(this, "shouldPushState", true);
60
+
50
61
  this.windowTitle = windowTitle;
51
62
  this.writeTimer = undefined;
52
63
  this.writeDelay = writeDelay;
53
64
  this._createURL = createURL;
54
65
  this.parseURL = parseURL;
55
- var title = this.windowTitle && this.windowTitle(this.read());
56
- setWindowTitle(title);
66
+ this.getLocation = getLocation;
67
+ (0, _utils.safelyRunOnBrowser)(function () {
68
+ var title = _this.windowTitle && _this.windowTitle(_this.read());
69
+
70
+ setWindowTitle(title);
71
+ });
57
72
  }
58
73
  /**
59
74
  * Reads the URL and returns a syncable UI search state.
@@ -65,7 +80,7 @@ var BrowserHistory = /*#__PURE__*/function () {
65
80
  value: function read() {
66
81
  return this.parseURL({
67
82
  qsModule: _qs.default,
68
- location: window.location
83
+ location: this.getLocation()
69
84
  });
70
85
  }
71
86
  /**
@@ -75,20 +90,30 @@ var BrowserHistory = /*#__PURE__*/function () {
75
90
  }, {
76
91
  key: "write",
77
92
  value: function write(routeState) {
78
- var _this = this;
93
+ var _this2 = this;
79
94
 
80
- var url = this.createURL(routeState);
81
- var title = this.windowTitle && this.windowTitle(routeState);
95
+ (0, _utils.safelyRunOnBrowser)(function (_ref2) {
96
+ var window = _ref2.window;
82
97
 
83
- if (this.writeTimer) {
84
- window.clearTimeout(this.writeTimer);
85
- }
98
+ var url = _this2.createURL(routeState);
99
+
100
+ var title = _this2.windowTitle && _this2.windowTitle(routeState);
101
+
102
+ if (_this2.writeTimer) {
103
+ clearTimeout(_this2.writeTimer);
104
+ }
105
+
106
+ _this2.writeTimer = setTimeout(function () {
107
+ setWindowTitle(title);
86
108
 
87
- this.writeTimer = window.setTimeout(function () {
88
- setWindowTitle(title);
89
- window.history.pushState(routeState, title || '', url);
90
- _this.writeTimer = undefined;
91
- }, this.writeDelay);
109
+ if (_this2.shouldPushState) {
110
+ window.history.pushState(routeState, title || '', url);
111
+ }
112
+
113
+ _this2.shouldPushState = true;
114
+ _this2.writeTimer = undefined;
115
+ }, _this2.writeDelay);
116
+ });
92
117
  }
93
118
  /**
94
119
  * Sets a callback on the `onpopstate` event of the history API of the current page.
@@ -98,26 +123,30 @@ var BrowserHistory = /*#__PURE__*/function () {
98
123
  }, {
99
124
  key: "onUpdate",
100
125
  value: function onUpdate(callback) {
101
- var _this2 = this;
126
+ var _this3 = this;
102
127
 
103
128
  this._onPopState = function (event) {
104
- if (_this2.writeTimer) {
105
- window.clearTimeout(_this2.writeTimer);
106
- _this2.writeTimer = undefined;
129
+ if (_this3.writeTimer) {
130
+ clearTimeout(_this3.writeTimer);
131
+ _this3.writeTimer = undefined;
107
132
  }
108
133
 
134
+ _this3.shouldPushState = false;
109
135
  var routeState = event.state; // At initial load, the state is read from the URL without update.
110
136
  // Therefore the state object is not available.
111
137
  // In this case, we fallback and read the URL.
112
138
 
113
139
  if (!routeState) {
114
- callback(_this2.read());
140
+ callback(_this3.read());
115
141
  } else {
116
142
  callback(routeState);
117
143
  }
118
144
  };
119
145
 
120
- window.addEventListener('popstate', this._onPopState);
146
+ (0, _utils.safelyRunOnBrowser)(function (_ref3) {
147
+ var window = _ref3.window;
148
+ window.addEventListener('popstate', _this3._onPopState);
149
+ });
121
150
  }
122
151
  /**
123
152
  * Creates a complete URL from a given syncable UI state.
@@ -133,7 +162,7 @@ var BrowserHistory = /*#__PURE__*/function () {
133
162
  return this._createURL({
134
163
  qsModule: _qs.default,
135
164
  routeState: routeState,
136
- location: window.location
165
+ location: this.getLocation()
137
166
  });
138
167
  }
139
168
  /**
@@ -143,12 +172,18 @@ var BrowserHistory = /*#__PURE__*/function () {
143
172
  }, {
144
173
  key: "dispose",
145
174
  value: function dispose() {
146
- if (this._onPopState) {
147
- window.removeEventListener('popstate', this._onPopState);
148
- }
175
+ var _this4 = this;
176
+
177
+ (0, _utils.safelyRunOnBrowser)(function (_ref4) {
178
+ var window = _ref4.window;
179
+
180
+ if (_this4._onPopState) {
181
+ window.removeEventListener('popstate', _this4._onPopState);
182
+ }
183
+ });
149
184
 
150
185
  if (this.writeTimer) {
151
- window.clearTimeout(this.writeTimer);
186
+ clearTimeout(this.writeTimer);
152
187
  }
153
188
 
154
189
  this.write({});
@@ -159,12 +194,12 @@ var BrowserHistory = /*#__PURE__*/function () {
159
194
  }();
160
195
 
161
196
  function historyRouter() {
162
- var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
163
- _ref2$createURL = _ref2.createURL,
164
- createURL = _ref2$createURL === void 0 ? function (_ref3) {
165
- var qsModule = _ref3.qsModule,
166
- routeState = _ref3.routeState,
167
- location = _ref3.location;
197
+ var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
198
+ _ref5$createURL = _ref5.createURL,
199
+ createURL = _ref5$createURL === void 0 ? function (_ref6) {
200
+ var qsModule = _ref6.qsModule,
201
+ routeState = _ref6.routeState,
202
+ location = _ref6.location;
168
203
  var protocol = location.protocol,
169
204
  hostname = location.hostname,
170
205
  _location$port = location.port,
@@ -180,11 +215,11 @@ function historyRouter() {
180
215
  }
181
216
 
182
217
  return "".concat(protocol, "//").concat(hostname).concat(portWithPrefix).concat(pathname, "?").concat(queryString).concat(hash);
183
- } : _ref2$createURL,
184
- _ref2$parseURL = _ref2.parseURL,
185
- parseURL = _ref2$parseURL === void 0 ? function (_ref4) {
186
- var qsModule = _ref4.qsModule,
187
- location = _ref4.location;
218
+ } : _ref5$createURL,
219
+ _ref5$parseURL = _ref5.parseURL,
220
+ parseURL = _ref5$parseURL === void 0 ? function (_ref7) {
221
+ var qsModule = _ref7.qsModule,
222
+ location = _ref7.location;
188
223
  // `qs` by default converts arrays with more than 20 items to an object.
189
224
  // We want to avoid this because the data structure manipulated can therefore vary.
190
225
  // Setting the limit to `100` seems a good number because the engine's default is 100
@@ -198,15 +233,27 @@ function historyRouter() {
198
233
  return qsModule.parse(location.search.slice(1), {
199
234
  arrayLimit: 99
200
235
  });
201
- } : _ref2$parseURL,
202
- _ref2$writeDelay = _ref2.writeDelay,
203
- writeDelay = _ref2$writeDelay === void 0 ? 400 : _ref2$writeDelay,
204
- windowTitle = _ref2.windowTitle;
236
+ } : _ref5$parseURL,
237
+ _ref5$writeDelay = _ref5.writeDelay,
238
+ writeDelay = _ref5$writeDelay === void 0 ? 400 : _ref5$writeDelay,
239
+ windowTitle = _ref5.windowTitle,
240
+ _ref5$getLocation = _ref5.getLocation,
241
+ getLocation = _ref5$getLocation === void 0 ? function () {
242
+ return (0, _utils.safelyRunOnBrowser)(function (_ref8) {
243
+ var window = _ref8.window;
244
+ return window.location;
245
+ }, {
246
+ fallback: function fallback() {
247
+ throw new Error('You need to provide `getLocation` to the `history` router in environments where `window` does not exist.');
248
+ }
249
+ });
250
+ } : _ref5$getLocation;
205
251
 
206
252
  return new BrowserHistory({
207
253
  createURL: createURL,
208
254
  parseURL: parseURL,
209
255
  writeDelay: writeDelay,
210
- windowTitle: windowTitle
256
+ windowTitle: windowTitle,
257
+ getLocation: getLocation
211
258
  });
212
259
  }