@algolia/autocomplete-core 1.4.0 → 1.5.2

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 (49) hide show
  1. package/dist/esm/createAutocomplete.d.ts +8 -2
  2. package/dist/esm/createAutocomplete.js +9 -1
  3. package/dist/esm/createStore.js +4 -2
  4. package/dist/esm/getAutocompleteSetters.js +1 -1
  5. package/dist/esm/getDefaultProps.js +2 -2
  6. package/dist/esm/getPropGetters.js +41 -13
  7. package/dist/esm/index.d.ts +0 -1
  8. package/dist/esm/index.js +1 -2
  9. package/dist/esm/metadata.d.ts +33 -0
  10. package/dist/esm/metadata.js +44 -0
  11. package/dist/esm/onInput.d.ts +2 -1
  12. package/dist/esm/onInput.js +63 -39
  13. package/dist/esm/onKeyDown.js +10 -3
  14. package/dist/esm/reshape.js +1 -1
  15. package/dist/esm/resolve.js +2 -2
  16. package/dist/esm/stateReducer.js +1 -1
  17. package/dist/esm/types/AutocompleteApi.js +1 -0
  18. package/dist/esm/types/AutocompleteCollection.js +1 -0
  19. package/dist/esm/types/AutocompleteContext.js +1 -0
  20. package/dist/esm/types/AutocompleteEnvironment.d.ts +1 -0
  21. package/dist/esm/types/AutocompleteEnvironment.js +1 -0
  22. package/dist/esm/types/AutocompleteNavigator.js +1 -0
  23. package/dist/esm/types/AutocompleteOptions.js +1 -0
  24. package/dist/esm/types/AutocompletePlugin.d.ts +10 -0
  25. package/dist/esm/types/AutocompletePlugin.js +1 -0
  26. package/dist/esm/types/AutocompletePropGetters.js +1 -0
  27. package/dist/esm/types/AutocompleteReshape.js +1 -0
  28. package/dist/esm/types/AutocompleteSetters.js +1 -0
  29. package/dist/esm/types/AutocompleteSource.js +1 -0
  30. package/dist/esm/types/AutocompleteState.js +1 -0
  31. package/dist/esm/types/AutocompleteStore.d.ts +2 -0
  32. package/dist/esm/types/AutocompleteStore.js +1 -0
  33. package/dist/esm/types/AutocompleteSubscribers.js +1 -0
  34. package/dist/esm/utils/createCancelablePromise.d.ts +15 -0
  35. package/dist/esm/utils/createCancelablePromise.js +70 -0
  36. package/dist/esm/utils/createCancelablePromiseList.d.ts +7 -0
  37. package/dist/esm/utils/createCancelablePromiseList.js +21 -0
  38. package/dist/esm/utils/createConcurrentSafePromise.d.ts +1 -1
  39. package/dist/esm/utils/getNormalizedSources.js +1 -1
  40. package/dist/esm/utils/index.d.ts +2 -0
  41. package/dist/esm/utils/index.js +2 -0
  42. package/dist/esm/utils/mapToAlgoliaResponse.js +1 -1
  43. package/dist/umd/index.development.js +396 -169
  44. package/dist/umd/index.development.js.map +1 -1
  45. package/dist/umd/index.production.js +2 -2
  46. package/dist/umd/index.production.js.map +1 -1
  47. package/package.json +3 -3
  48. package/dist/esm/version.d.ts +0 -1
  49. package/dist/esm/version.js +0 -1
@@ -1,49 +1,22 @@
1
- /*! @algolia/autocomplete-core 1.4.0 | MIT License | © Algolia, Inc. and contributors | https://github.com/algolia/autocomplete */
1
+ /*! @algolia/autocomplete-core 1.5.2 | MIT License | © Algolia, Inc. and contributors | https://github.com/algolia/autocomplete */
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4
4
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
5
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global['@algolia/autocomplete-core'] = {}));
6
- }(this, (function (exports) { 'use strict';
7
-
8
- function _typeof(obj) {
9
- "@babel/helpers - typeof";
10
-
11
- if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
12
- _typeof = function (obj) {
13
- return typeof obj;
14
- };
15
- } else {
16
- _typeof = function (obj) {
17
- return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
18
- };
19
- }
20
-
21
- return _typeof(obj);
22
- }
23
-
24
- function _defineProperty(obj, key, value) {
25
- if (key in obj) {
26
- Object.defineProperty(obj, key, {
27
- value: value,
28
- enumerable: true,
29
- configurable: true,
30
- writable: true
31
- });
32
- } else {
33
- obj[key] = value;
34
- }
35
-
36
- return obj;
37
- }
5
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@algolia/autocomplete-core"] = {}));
6
+ })(this, (function (exports) { 'use strict';
38
7
 
39
8
  function ownKeys(object, enumerableOnly) {
40
9
  var keys = Object.keys(object);
41
10
 
42
11
  if (Object.getOwnPropertySymbols) {
43
12
  var symbols = Object.getOwnPropertySymbols(object);
44
- if (enumerableOnly) symbols = symbols.filter(function (sym) {
45
- return Object.getOwnPropertyDescriptor(object, sym).enumerable;
46
- });
13
+
14
+ if (enumerableOnly) {
15
+ symbols = symbols.filter(function (sym) {
16
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
17
+ });
18
+ }
19
+
47
20
  keys.push.apply(keys, symbols);
48
21
  }
49
22
 
@@ -70,6 +43,37 @@
70
43
  return target;
71
44
  }
72
45
 
46
+ function _typeof$1(obj) {
47
+ "@babel/helpers - typeof";
48
+
49
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
50
+ _typeof$1 = function (obj) {
51
+ return typeof obj;
52
+ };
53
+ } else {
54
+ _typeof$1 = function (obj) {
55
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
56
+ };
57
+ }
58
+
59
+ return _typeof$1(obj);
60
+ }
61
+
62
+ function _defineProperty(obj, key, value) {
63
+ if (key in obj) {
64
+ Object.defineProperty(obj, key, {
65
+ value: value,
66
+ enumerable: true,
67
+ configurable: true,
68
+ writable: true
69
+ });
70
+ } else {
71
+ obj[key] = value;
72
+ }
73
+
74
+ return obj;
75
+ }
76
+
73
77
  function _objectWithoutPropertiesLoose(source, excluded) {
74
78
  if (source == null) return {};
75
79
  var target = {};
@@ -107,27 +111,27 @@
107
111
  }
108
112
 
109
113
  function _toConsumableArray(arr) {
110
- return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
114
+ return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray$1(arr) || _nonIterableSpread();
111
115
  }
112
116
 
113
117
  function _arrayWithoutHoles(arr) {
114
- if (Array.isArray(arr)) return _arrayLikeToArray(arr);
118
+ if (Array.isArray(arr)) return _arrayLikeToArray$1(arr);
115
119
  }
116
120
 
117
121
  function _iterableToArray(iter) {
118
- if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
122
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
119
123
  }
120
124
 
121
- function _unsupportedIterableToArray(o, minLen) {
125
+ function _unsupportedIterableToArray$1(o, minLen) {
122
126
  if (!o) return;
123
- if (typeof o === "string") return _arrayLikeToArray(o, minLen);
127
+ if (typeof o === "string") return _arrayLikeToArray$1(o, minLen);
124
128
  var n = Object.prototype.toString.call(o).slice(8, -1);
125
129
  if (n === "Object" && o.constructor) n = o.constructor.name;
126
130
  if (n === "Map" || n === "Set") return Array.from(o);
127
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
131
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$1(o, minLen);
128
132
  }
129
133
 
130
- function _arrayLikeToArray(arr, len) {
134
+ function _arrayLikeToArray$1(arr, len) {
131
135
  if (len == null || len > arr.length) len = arr.length;
132
136
 
133
137
  for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
@@ -140,23 +144,23 @@
140
144
  }
141
145
 
142
146
  function _slicedToArray(arr, i) {
143
- return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray$1(arr, i) || _nonIterableRest();
147
+ return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
144
148
  }
145
149
 
146
150
  function _nonIterableRest() {
147
151
  throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
148
152
  }
149
153
 
150
- function _unsupportedIterableToArray$1(o, minLen) {
154
+ function _unsupportedIterableToArray(o, minLen) {
151
155
  if (!o) return;
152
- if (typeof o === "string") return _arrayLikeToArray$1(o, minLen);
156
+ if (typeof o === "string") return _arrayLikeToArray(o, minLen);
153
157
  var n = Object.prototype.toString.call(o).slice(8, -1);
154
158
  if (n === "Object" && o.constructor) n = o.constructor.name;
155
159
  if (n === "Map" || n === "Set") return Array.from(o);
156
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$1(o, minLen);
160
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
157
161
  }
158
162
 
159
- function _arrayLikeToArray$1(arr, len) {
163
+ function _arrayLikeToArray(arr, len) {
160
164
  if (len == null || len > arr.length) len = arr.length;
161
165
 
162
166
  for (var i = 0, arr2 = new Array(len); i < len; i++) {
@@ -167,14 +171,17 @@
167
171
  }
168
172
 
169
173
  function _iterableToArrayLimit(arr, i) {
170
- if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
174
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
175
+
176
+ if (_i == null) return;
171
177
  var _arr = [];
172
178
  var _n = true;
173
179
  var _d = false;
174
- var _e = undefined;
180
+
181
+ var _s, _e;
175
182
 
176
183
  try {
177
- for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
184
+ for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {
178
185
  _arr.push(_s.value);
179
186
 
180
187
  if (i && _arr.length === i) break;
@@ -197,20 +204,20 @@
197
204
  if (Array.isArray(arr)) return arr;
198
205
  }
199
206
 
200
- function _typeof$1(obj) {
207
+ function _typeof(obj) {
201
208
  "@babel/helpers - typeof";
202
209
 
203
210
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
204
- _typeof$1 = function _typeof(obj) {
211
+ _typeof = function _typeof(obj) {
205
212
  return typeof obj;
206
213
  };
207
214
  } else {
208
- _typeof$1 = function _typeof(obj) {
215
+ _typeof = function _typeof(obj) {
209
216
  return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
210
217
  };
211
218
  }
212
219
 
213
- return _typeof$1(obj);
220
+ return _typeof(obj);
214
221
  }
215
222
  /**
216
223
  * Decycles objects with circular references.
@@ -221,7 +228,7 @@
221
228
  function decycle(obj) {
222
229
  var seen = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Set();
223
230
 
224
- if ( !obj || _typeof$1(obj) !== 'object') {
231
+ if (!obj || _typeof(obj) !== 'object') {
225
232
  return obj;
226
233
  }
227
234
 
@@ -281,6 +288,13 @@
281
288
 
282
289
  var noop = function noop() {};
283
290
 
291
+ var version = '1.5.2';
292
+
293
+ var userAgents = [{
294
+ segment: 'autocomplete-core',
295
+ version: version
296
+ }];
297
+
284
298
  var warnCache = {
285
299
  current: {}
286
300
  };
@@ -309,74 +323,117 @@
309
323
  "development" !== 'production' ? warn(!options.debug, 'The `debug` option is meant for development debugging and should not be used in production.') : void 0;
310
324
  }
311
325
 
312
- function createStore(reducer, props, onStoreStateChange) {
313
- var state = props.initialState;
326
+ function createInternalCancelablePromise(promise, initialState) {
327
+ var state = initialState;
314
328
  return {
315
- getState: function getState() {
316
- return state;
329
+ then: function then(onfulfilled, onrejected) {
330
+ return createInternalCancelablePromise(promise.then(createCallback(onfulfilled, state, promise), createCallback(onrejected, state, promise)), state);
317
331
  },
318
- dispatch: function dispatch(action, payload) {
319
- var prevState = _objectSpread2({}, state);
332
+ catch: function _catch(onrejected) {
333
+ return createInternalCancelablePromise(promise.catch(createCallback(onrejected, state, promise)), state);
334
+ },
335
+ finally: function _finally(onfinally) {
336
+ if (onfinally) {
337
+ state.onCancelList.push(onfinally);
338
+ }
320
339
 
321
- state = reducer(state, {
322
- type: action,
323
- props: props,
324
- payload: payload
325
- });
326
- onStoreStateChange({
327
- state: state,
328
- prevState: prevState
340
+ return createInternalCancelablePromise(promise.finally(createCallback(onfinally && function () {
341
+ state.onCancelList = [];
342
+ return onfinally();
343
+ }, state, promise)), state);
344
+ },
345
+ cancel: function cancel() {
346
+ state.isCanceled = true;
347
+ var callbacks = state.onCancelList;
348
+ state.onCancelList = [];
349
+ callbacks.forEach(function (callback) {
350
+ callback();
329
351
  });
352
+ },
353
+ isCanceled: function isCanceled() {
354
+ return state.isCanceled === true;
330
355
  }
331
356
  };
332
357
  }
333
358
 
334
- function getAutocompleteSetters(_ref) {
335
- var store = _ref.store;
336
-
337
- var setActiveItemId = function setActiveItemId(value) {
338
- store.dispatch('setActiveItemId', value);
339
- };
359
+ function cancelable(promise) {
360
+ return createInternalCancelablePromise(promise, {
361
+ isCanceled: false,
362
+ onCancelList: []
363
+ });
364
+ }
340
365
 
341
- var setQuery = function setQuery(value) {
342
- store.dispatch('setQuery', value);
343
- };
366
+ function createCallback(onResult, state, fallback) {
367
+ if (!onResult) {
368
+ return fallback;
369
+ }
344
370
 
345
- var setCollections = function setCollections(rawValue) {
346
- var baseItemId = 0;
347
- var value = rawValue.map(function (collection) {
348
- return _objectSpread2(_objectSpread2({}, collection), {}, {
349
- // We flatten the stored items to support calling `getAlgoliaResults`
350
- // from the source itself.
351
- items: flatten(collection.items).map(function (item) {
352
- return _objectSpread2(_objectSpread2({}, item), {}, {
353
- __autocomplete_id: baseItemId++
354
- });
355
- })
356
- });
357
- });
358
- store.dispatch('setCollections', value);
359
- };
371
+ return function callback(arg) {
372
+ if (state.isCanceled) {
373
+ return arg;
374
+ }
360
375
 
361
- var setIsOpen = function setIsOpen(value) {
362
- store.dispatch('setIsOpen', value);
376
+ return onResult(arg);
363
377
  };
378
+ }
364
379
 
365
- var setStatus = function setStatus(value) {
366
- store.dispatch('setStatus', value);
380
+ function createCancelablePromiseList() {
381
+ var list = [];
382
+ return {
383
+ add: function add(cancelablePromise) {
384
+ list.push(cancelablePromise);
385
+ return cancelablePromise.finally(function () {
386
+ list = list.filter(function (item) {
387
+ return item !== cancelablePromise;
388
+ });
389
+ });
390
+ },
391
+ cancelAll: function cancelAll() {
392
+ list.forEach(function (promise) {
393
+ return promise.cancel();
394
+ });
395
+ },
396
+ isEmpty: function isEmpty() {
397
+ return list.length === 0;
398
+ }
367
399
  };
400
+ }
368
401
 
369
- var setContext = function setContext(value) {
370
- store.dispatch('setContext', value);
371
- };
402
+ /**
403
+ * Creates a runner that executes promises in a concurrent-safe way.
404
+ *
405
+ * This is useful to prevent older promises to resolve after a newer promise,
406
+ * otherwise resulting in stale resolved values.
407
+ */
408
+ function createConcurrentSafePromise() {
409
+ var basePromiseId = -1;
410
+ var latestResolvedId = -1;
411
+ var latestResolvedValue = undefined;
412
+ return function runConcurrentSafePromise(promise) {
413
+ basePromiseId++;
414
+ var currentPromiseId = basePromiseId;
415
+ return Promise.resolve(promise).then(function (x) {
416
+ // The promise might take too long to resolve and get outdated. This would
417
+ // result in resolving stale values.
418
+ // When this happens, we ignore the promise value and return the one
419
+ // coming from the latest resolved value.
420
+ //
421
+ // +----------------------------------+
422
+ // | 100ms |
423
+ // | run(1) +---> R1 |
424
+ // | 300ms |
425
+ // | run(2) +-------------> R2 (SKIP) |
426
+ // | 200ms |
427
+ // | run(3) +--------> R3 |
428
+ // +----------------------------------+
429
+ if (latestResolvedValue && currentPromiseId < latestResolvedId) {
430
+ return latestResolvedValue;
431
+ }
372
432
 
373
- return {
374
- setActiveItemId: setActiveItemId,
375
- setQuery: setQuery,
376
- setCollections: setCollections,
377
- setIsOpen: setIsOpen,
378
- setStatus: setStatus,
379
- setContext: setContext
433
+ latestResolvedId = currentPromiseId;
434
+ latestResolvedValue = x;
435
+ return x;
436
+ });
380
437
  };
381
438
  }
382
439
 
@@ -418,7 +475,7 @@
418
475
  var seenSourceIds = [];
419
476
  return Promise.resolve(getSources(params)).then(function (sources) {
420
477
  invariant(Array.isArray(sources), function () {
421
- return "The `getSources` function must return an array of sources but returned type ".concat(JSON.stringify(_typeof(sources)), ":\n\n").concat(JSON.stringify(decycle(sources), null, 2));
478
+ return "The `getSources` function must return an array of sources but returned type ".concat(JSON.stringify(_typeof$1(sources)), ":\n\n").concat(JSON.stringify(decycle(sources), null, 2));
422
479
  });
423
480
  return Promise.all(sources // We allow `undefined` and `false` sources to allow users to use
424
481
  // `Boolean(query) && source` (=> `false`).
@@ -582,6 +639,78 @@
582
639
  };
583
640
  }
584
641
 
642
+ function createStore(reducer, props, onStoreStateChange) {
643
+ var state = props.initialState;
644
+ return {
645
+ getState: function getState() {
646
+ return state;
647
+ },
648
+ dispatch: function dispatch(action, payload) {
649
+ var prevState = _objectSpread2({}, state);
650
+
651
+ state = reducer(state, {
652
+ type: action,
653
+ props: props,
654
+ payload: payload
655
+ });
656
+ onStoreStateChange({
657
+ state: state,
658
+ prevState: prevState
659
+ });
660
+ },
661
+ pendingRequests: createCancelablePromiseList()
662
+ };
663
+ }
664
+
665
+ function getAutocompleteSetters(_ref) {
666
+ var store = _ref.store;
667
+
668
+ var setActiveItemId = function setActiveItemId(value) {
669
+ store.dispatch('setActiveItemId', value);
670
+ };
671
+
672
+ var setQuery = function setQuery(value) {
673
+ store.dispatch('setQuery', value);
674
+ };
675
+
676
+ var setCollections = function setCollections(rawValue) {
677
+ var baseItemId = 0;
678
+ var value = rawValue.map(function (collection) {
679
+ return _objectSpread2(_objectSpread2({}, collection), {}, {
680
+ // We flatten the stored items to support calling `getAlgoliaResults`
681
+ // from the source itself.
682
+ items: flatten(collection.items).map(function (item) {
683
+ return _objectSpread2(_objectSpread2({}, item), {}, {
684
+ __autocomplete_id: baseItemId++
685
+ });
686
+ })
687
+ });
688
+ });
689
+ store.dispatch('setCollections', value);
690
+ };
691
+
692
+ var setIsOpen = function setIsOpen(value) {
693
+ store.dispatch('setIsOpen', value);
694
+ };
695
+
696
+ var setStatus = function setStatus(value) {
697
+ store.dispatch('setStatus', value);
698
+ };
699
+
700
+ var setContext = function setContext(value) {
701
+ store.dispatch('setContext', value);
702
+ };
703
+
704
+ return {
705
+ setActiveItemId: setActiveItemId,
706
+ setQuery: setQuery,
707
+ setCollections: setCollections,
708
+ setIsOpen: setIsOpen,
709
+ setStatus: setStatus,
710
+ setContext: setContext
711
+ };
712
+ }
713
+
585
714
  function getDefaultProps(props, pluginSubscribers) {
586
715
  var _props$id;
587
716
 
@@ -812,7 +941,7 @@
812
941
  var transform = matches[0].transformResponse;
813
942
  var items = transform ? transform(mapToAlgoliaResponse(results)) : results;
814
943
  invariant(Array.isArray(items), function () {
815
- return "The `getItems` function from source \"".concat(source.sourceId, "\" must return an array of items but returned type ").concat(JSON.stringify(_typeof(items)), ":\n\n").concat(JSON.stringify(decycle(items), null, 2), ".\n\nSee: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/#param-getitems");
944
+ return "The `getItems` function from source \"".concat(source.sourceId, "\" must return an array of items but returned type ").concat(JSON.stringify(_typeof$1(items)), ":\n\n").concat(JSON.stringify(decycle(items), null, 2), ".\n\nSee: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/#param-getitems");
816
945
  });
817
946
  invariant(items.every(Boolean), "The `getItems` function from source \"".concat(source.sourceId, "\" must return an array of items but returned ").concat(JSON.stringify(undefined), ".\n\nDid you forget to return items?\n\nSee: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/#param-getitems"));
818
947
  return {
@@ -822,7 +951,9 @@
822
951
  });
823
952
  }
824
953
 
954
+ var _excluded$2 = ["event", "nextState", "props", "query", "refresh", "store"];
825
955
  var lastStalledId = null;
956
+ var runConcurrentSafePromise = createConcurrentSafePromise();
826
957
  function onInput(_ref) {
827
958
  var event = _ref.event,
828
959
  _ref$nextState = _ref.nextState,
@@ -831,7 +962,7 @@
831
962
  query = _ref.query,
832
963
  refresh = _ref.refresh,
833
964
  store = _ref.store,
834
- setters = _objectWithoutProperties(_ref, ["event", "nextState", "props", "query", "refresh", "store"]);
965
+ setters = _objectWithoutProperties(_ref, _excluded$2);
835
966
 
836
967
  if (lastStalledId) {
837
968
  props.environment.clearTimeout(lastStalledId);
@@ -848,28 +979,42 @@
848
979
  if (!query && props.openOnFocus === false) {
849
980
  var _nextState$isOpen;
850
981
 
851
- setStatus('idle');
852
- setCollections(store.getState().collections.map(function (collection) {
982
+ var collections = store.getState().collections.map(function (collection) {
853
983
  return _objectSpread2(_objectSpread2({}, collection), {}, {
854
984
  items: []
855
985
  });
856
- }));
986
+ });
987
+ setStatus('idle');
988
+ setCollections(collections);
857
989
  setIsOpen((_nextState$isOpen = nextState.isOpen) !== null && _nextState$isOpen !== void 0 ? _nextState$isOpen : props.shouldPanelOpen({
858
990
  state: store.getState()
991
+ })); // We make sure to update the latest resolved value of the tracked
992
+ // promises to keep late resolving promises from "cancelling" the state
993
+ // updates performed in this code path.
994
+ // We chain with a void promise to respect `onInput`'s expected return type.
995
+
996
+ var _request = cancelable(runConcurrentSafePromise(collections).then(function () {
997
+ return Promise.resolve();
859
998
  }));
860
- return Promise.resolve();
999
+
1000
+ return store.pendingRequests.add(_request);
861
1001
  }
862
1002
 
863
1003
  setStatus('loading');
864
1004
  lastStalledId = props.environment.setTimeout(function () {
865
1005
  setStatus('stalled');
866
- }, props.stallThreshold);
867
- return props.getSources(_objectSpread2({
1006
+ }, props.stallThreshold); // We track the entire promise chain triggered by `onInput` before mutating
1007
+ // the Autocomplete state to make sure that any state manipulation is based on
1008
+ // fresh data regardless of when promises individually resolve.
1009
+ // We don't track nested promises and only rely on the full chain resolution,
1010
+ // meaning we should only ever manipulate the state once this concurrent-safe
1011
+ // promise is resolved.
1012
+
1013
+ var request = cancelable(runConcurrentSafePromise(props.getSources(_objectSpread2({
868
1014
  query: query,
869
1015
  refresh: refresh,
870
1016
  state: store.getState()
871
1017
  }, setters)).then(function (sources) {
872
- setStatus('loading');
873
1018
  return Promise.all(sources.map(function (source) {
874
1019
  return Promise.resolve(source.getItems(_objectSpread2({
875
1020
  query: query,
@@ -886,46 +1031,54 @@
886
1031
  props: props,
887
1032
  state: store.getState()
888
1033
  });
889
- }).then(function (collections) {
890
- var _nextState$isOpen2;
1034
+ });
1035
+ }))).then(function (collections) {
1036
+ var _nextState$isOpen2;
891
1037
 
892
- setStatus('idle');
893
- setCollections(collections);
894
- var isPanelOpen = props.shouldPanelOpen({
1038
+ // Parameters passed to `onInput` could be stale when the following code
1039
+ // executes, because `onInput` calls may not resolve in order.
1040
+ // If it becomes a problem we'll need to save the last passed parameters.
1041
+ // See: https://codesandbox.io/s/agitated-cookies-y290z
1042
+ setStatus('idle');
1043
+ setCollections(collections);
1044
+ var isPanelOpen = props.shouldPanelOpen({
1045
+ state: store.getState()
1046
+ });
1047
+ setIsOpen((_nextState$isOpen2 = nextState.isOpen) !== null && _nextState$isOpen2 !== void 0 ? _nextState$isOpen2 : props.openOnFocus && !query && isPanelOpen || isPanelOpen);
1048
+ var highlightedItem = getActiveItem(store.getState());
1049
+
1050
+ if (store.getState().activeItemId !== null && highlightedItem) {
1051
+ var item = highlightedItem.item,
1052
+ itemInputValue = highlightedItem.itemInputValue,
1053
+ itemUrl = highlightedItem.itemUrl,
1054
+ source = highlightedItem.source;
1055
+ source.onActive(_objectSpread2({
1056
+ event: event,
1057
+ item: item,
1058
+ itemInputValue: itemInputValue,
1059
+ itemUrl: itemUrl,
1060
+ refresh: refresh,
1061
+ source: source,
895
1062
  state: store.getState()
896
- });
897
- setIsOpen((_nextState$isOpen2 = nextState.isOpen) !== null && _nextState$isOpen2 !== void 0 ? _nextState$isOpen2 : props.openOnFocus && !query && isPanelOpen || isPanelOpen);
898
- var highlightedItem = getActiveItem(store.getState());
1063
+ }, setters));
1064
+ }
1065
+ }).finally(function () {
1066
+ setStatus('idle');
899
1067
 
900
- if (store.getState().activeItemId !== null && highlightedItem) {
901
- var item = highlightedItem.item,
902
- itemInputValue = highlightedItem.itemInputValue,
903
- itemUrl = highlightedItem.itemUrl,
904
- source = highlightedItem.source;
905
- source.onActive(_objectSpread2({
906
- event: event,
907
- item: item,
908
- itemInputValue: itemInputValue,
909
- itemUrl: itemUrl,
910
- refresh: refresh,
911
- source: source,
912
- state: store.getState()
913
- }, setters));
914
- }
915
- }).finally(function () {
916
- if (lastStalledId) {
917
- props.environment.clearTimeout(lastStalledId);
918
- }
919
- });
1068
+ if (lastStalledId) {
1069
+ props.environment.clearTimeout(lastStalledId);
1070
+ }
920
1071
  });
1072
+ return store.pendingRequests.add(request);
921
1073
  }
922
1074
 
1075
+ var _excluded$1 = ["event", "props", "refresh", "store"];
923
1076
  function onKeyDown(_ref) {
924
1077
  var event = _ref.event,
925
1078
  props = _ref.props,
926
1079
  refresh = _ref.refresh,
927
1080
  store = _ref.store,
928
- setters = _objectWithoutProperties(_ref, ["event", "props", "refresh", "store"]);
1081
+ setters = _objectWithoutProperties(_ref, _excluded$1);
929
1082
 
930
1083
  if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
931
1084
  // eslint-disable-next-line no-inner-declarations
@@ -994,7 +1147,12 @@
994
1147
  // from removing the query right away because we first want to close the
995
1148
  // panel.
996
1149
  event.preventDefault();
997
- store.dispatch(event.key, null);
1150
+ store.dispatch(event.key, null); // Hitting the `Escape` key signals the end of a user interaction with the
1151
+ // autocomplete. At this point, we should ignore any requests that are still
1152
+ // pending and could reopen the panel once they resolve, because that would
1153
+ // result in an unsolicited UI behavior.
1154
+
1155
+ store.pendingRequests.cancelAll();
998
1156
  } else if (event.key === 'Enter') {
999
1157
  // No active item, so we let the browser handle the native `onSubmit` form
1000
1158
  // event.
@@ -1091,17 +1249,22 @@
1091
1249
  }
1092
1250
  }
1093
1251
 
1252
+ var _excluded = ["props", "refresh", "store"],
1253
+ _excluded2 = ["inputElement", "formElement", "panelElement"],
1254
+ _excluded3 = ["inputElement"],
1255
+ _excluded4 = ["inputElement", "maxLength"],
1256
+ _excluded5 = ["item", "source"];
1094
1257
  function getPropGetters(_ref) {
1095
1258
  var props = _ref.props,
1096
1259
  refresh = _ref.refresh,
1097
1260
  store = _ref.store,
1098
- setters = _objectWithoutProperties(_ref, ["props", "refresh", "store"]);
1261
+ setters = _objectWithoutProperties(_ref, _excluded);
1099
1262
 
1100
1263
  var getEnvironmentProps = function getEnvironmentProps(providedProps) {
1101
1264
  var inputElement = providedProps.inputElement,
1102
1265
  formElement = providedProps.formElement,
1103
1266
  panelElement = providedProps.panelElement,
1104
- rest = _objectWithoutProperties(providedProps, ["inputElement", "formElement", "panelElement"]);
1267
+ rest = _objectWithoutProperties(providedProps, _excluded2);
1105
1268
 
1106
1269
  return _objectSpread2({
1107
1270
  // On touch devices, we do not rely on the native `blur` event of the
@@ -1109,19 +1272,34 @@
1109
1272
  // outside of the autocomplete elements.
1110
1273
  // This ensures a working experience on mobile because we blur the input
1111
1274
  // on touch devices when the user starts scrolling (`touchmove`).
1275
+ // @TODO: support cases where there are multiple Autocomplete instances.
1276
+ // Right now, a second instance makes this computation return false.
1112
1277
  onTouchStart: function onTouchStart(event) {
1113
- if (store.getState().isOpen === false || event.target === inputElement) {
1278
+ // The `onTouchStart` event shouldn't trigger the `blur` handler when
1279
+ // it's not an interaction with Autocomplete. We detect it with the
1280
+ // following heuristics:
1281
+ // - the panel is closed AND there are no pending requests
1282
+ // (no interaction with the autocomplete, no future state updates)
1283
+ // - OR the touched target is the input element (should open the panel)
1284
+ var isAutocompleteInteraction = store.getState().isOpen || !store.pendingRequests.isEmpty();
1285
+
1286
+ if (!isAutocompleteInteraction || event.target === inputElement) {
1114
1287
  return;
1115
- } // @TODO: support cases where there are multiple Autocomplete instances.
1116
- // Right now, a second instance makes this computation return false.
1117
-
1288
+ }
1118
1289
 
1119
1290
  var isTargetWithinAutocomplete = [formElement, panelElement].some(function (contextNode) {
1120
- return isOrContainsNode(contextNode, event.target) || isOrContainsNode(contextNode, props.environment.document.activeElement);
1291
+ return isOrContainsNode(contextNode, event.target);
1121
1292
  });
1122
1293
 
1123
1294
  if (isTargetWithinAutocomplete === false) {
1124
- store.dispatch('blur', null);
1295
+ store.dispatch('blur', null); // If requests are still pending when the user closes the panel, they
1296
+ // could reopen the panel once they resolve.
1297
+ // We want to prevent any subsequent query from reopening the panel
1298
+ // because it would result in an unsolicited UI behavior.
1299
+
1300
+ if (!props.debug) {
1301
+ store.pendingRequests.cancelAll();
1302
+ }
1125
1303
  }
1126
1304
  },
1127
1305
  // When scrolling on touch devices (mobiles, tablets, etc.), we want to
@@ -1149,8 +1327,8 @@
1149
1327
  };
1150
1328
 
1151
1329
  var getFormProps = function getFormProps(providedProps) {
1152
- var inputElement = providedProps.inputElement,
1153
- rest = _objectWithoutProperties(providedProps, ["inputElement"]);
1330
+ providedProps.inputElement;
1331
+ var rest = _objectWithoutProperties(providedProps, _excluded3);
1154
1332
 
1155
1333
  return _objectSpread2({
1156
1334
  action: '',
@@ -1202,11 +1380,11 @@
1202
1380
 
1203
1381
  var isTouchDevice = ('ontouchstart' in props.environment);
1204
1382
 
1205
- var _ref2 = providedProps || {},
1206
- inputElement = _ref2.inputElement,
1207
- _ref2$maxLength = _ref2.maxLength,
1383
+ var _ref2 = providedProps || {};
1384
+ _ref2.inputElement;
1385
+ var _ref2$maxLength = _ref2.maxLength,
1208
1386
  maxLength = _ref2$maxLength === void 0 ? 512 : _ref2$maxLength,
1209
- rest = _objectWithoutProperties(_ref2, ["inputElement", "maxLength"]);
1387
+ rest = _objectWithoutProperties(_ref2, _excluded4);
1210
1388
 
1211
1389
  var activeItem = getActiveItem(store.getState());
1212
1390
  return _objectSpread2({
@@ -1247,7 +1425,14 @@
1247
1425
  // We do rely on the `blur` event on touch devices.
1248
1426
  // See explanation in `onTouchStart`.
1249
1427
  if (!isTouchDevice) {
1250
- store.dispatch('blur', null);
1428
+ store.dispatch('blur', null); // If requests are still pending when the user closes the panel, they
1429
+ // could reopen the panel once they resolve.
1430
+ // We want to prevent any subsequent query from reopening the panel
1431
+ // because it would result in an unsolicited UI behavior.
1432
+
1433
+ if (!props.debug) {
1434
+ store.pendingRequests.cancelAll();
1435
+ }
1251
1436
  }
1252
1437
  },
1253
1438
  onClick: function onClick(event) {
@@ -1297,7 +1482,7 @@
1297
1482
  var getItemProps = function getItemProps(providedProps) {
1298
1483
  var item = providedProps.item,
1299
1484
  source = providedProps.source,
1300
- rest = _objectWithoutProperties(providedProps, ["item", "source"]);
1485
+ rest = _objectWithoutProperties(providedProps, _excluded5);
1301
1486
 
1302
1487
  return _objectSpread2({
1303
1488
  id: "".concat(props.id, "-item-").concat(item.__autocomplete_id),
@@ -1384,6 +1569,44 @@
1384
1569
  };
1385
1570
  }
1386
1571
 
1572
+ function getMetadata(_ref) {
1573
+ var _, _options$__autocomple, _options$__autocomple2, _options$__autocomple3;
1574
+
1575
+ var plugins = _ref.plugins,
1576
+ options = _ref.options;
1577
+ var optionsKey = (_ = (((_options$__autocomple = options.__autocomplete_metadata) === null || _options$__autocomple === void 0 ? void 0 : _options$__autocomple.userAgents) || [])[0]) === null || _ === void 0 ? void 0 : _.segment;
1578
+ var extraOptions = optionsKey ? _defineProperty({}, optionsKey, Object.keys(((_options$__autocomple2 = options.__autocomplete_metadata) === null || _options$__autocomple2 === void 0 ? void 0 : _options$__autocomple2.options) || {})) : {};
1579
+ return {
1580
+ plugins: plugins.map(function (plugin) {
1581
+ return {
1582
+ name: plugin.name,
1583
+ options: Object.keys(plugin.__autocomplete_pluginOptions || [])
1584
+ };
1585
+ }),
1586
+ options: _objectSpread2({
1587
+ 'autocomplete-core': Object.keys(options)
1588
+ }, extraOptions),
1589
+ ua: userAgents.concat(((_options$__autocomple3 = options.__autocomplete_metadata) === null || _options$__autocomple3 === void 0 ? void 0 : _options$__autocomple3.userAgents) || [])
1590
+ };
1591
+ }
1592
+ function injectMetadata(_ref3) {
1593
+ var _environment$navigato;
1594
+
1595
+ var metadata = _ref3.metadata,
1596
+ environment = _ref3.environment;
1597
+ var isMetadataEnabled = (_environment$navigato = environment.navigator) === null || _environment$navigato === void 0 ? void 0 : _environment$navigato.userAgent.includes('Algolia Crawler');
1598
+
1599
+ if (isMetadataEnabled) {
1600
+ var metadataContainer = environment.document.createElement('meta');
1601
+ var headRef = environment.document.querySelector('head');
1602
+ metadataContainer.name = 'algolia:metadata';
1603
+ setTimeout(function () {
1604
+ metadataContainer.content = JSON.stringify(metadata);
1605
+ headRef.appendChild(metadataContainer);
1606
+ }, 0);
1607
+ }
1608
+ }
1609
+
1387
1610
  function getCompletion(_ref) {
1388
1611
  var _getActiveItem;
1389
1612
 
@@ -1603,18 +1826,22 @@
1603
1826
  }
1604
1827
  }));
1605
1828
  });
1829
+ injectMetadata({
1830
+ metadata: getMetadata({
1831
+ plugins: props.plugins,
1832
+ options: options
1833
+ }),
1834
+ environment: props.environment
1835
+ });
1606
1836
  return _objectSpread2(_objectSpread2({
1607
1837
  refresh: refresh
1608
1838
  }, propGetters), setters);
1609
1839
  }
1610
1840
 
1611
- var version = '1.4.0';
1612
-
1613
1841
  exports.createAutocomplete = createAutocomplete;
1614
1842
  exports.getDefaultProps = getDefaultProps;
1615
- exports.version = version;
1616
1843
 
1617
1844
  Object.defineProperty(exports, '__esModule', { value: true });
1618
1845
 
1619
- })));
1846
+ }));
1620
1847
  //# sourceMappingURL=index.development.js.map