@converse/skeletor 0.0.8 → 0.0.9

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 (42) hide show
  1. package/README.md +38 -25
  2. package/dist/skeletor.js +9232 -5096
  3. package/dist/skeletor.js.map +1 -1
  4. package/dist/skeletor.min.js +74 -3
  5. package/dist/skeletor.min.js.map +1 -1
  6. package/package.json +21 -11
  7. package/src/collection.js +540 -395
  8. package/src/drivers/sessionStorage.js +222 -219
  9. package/src/element.js +132 -82
  10. package/src/eventemitter.js +187 -0
  11. package/src/helpers.js +143 -118
  12. package/src/{main.js → index.js} +21 -17
  13. package/src/listening.js +81 -0
  14. package/src/model.js +340 -238
  15. package/src/storage.js +222 -230
  16. package/src/types/collection.d.ts +366 -0
  17. package/src/types/collection.d.ts.map +1 -0
  18. package/src/types/drivers/sessionStorage.d.ts +37 -0
  19. package/src/types/drivers/sessionStorage.d.ts.map +1 -0
  20. package/src/types/element.d.ts +115 -0
  21. package/src/types/element.d.ts.map +1 -0
  22. package/src/types/eventemitter.d.ts +93 -0
  23. package/src/types/eventemitter.d.ts.map +1 -0
  24. package/src/types/helpers.d.ts +62 -0
  25. package/src/types/helpers.d.ts.map +1 -0
  26. package/src/types/index.d.ts +19 -0
  27. package/src/types/index.d.ts.map +1 -0
  28. package/src/types/listening.d.ts +40 -0
  29. package/src/types/listening.d.ts.map +1 -0
  30. package/src/types/model.d.ts +230 -0
  31. package/src/types/model.d.ts.map +1 -0
  32. package/src/types/storage.d.ts +35 -0
  33. package/src/types/storage.d.ts.map +1 -0
  34. package/src/types/utils/events.d.ts +28 -0
  35. package/src/types/utils/events.d.ts.map +1 -0
  36. package/src/utils/events.js +174 -0
  37. package/src/drivers/sessionStorage.js~ +0 -307
  38. package/src/events.js +0 -328
  39. package/src/history.js +0 -301
  40. package/src/overview.js +0 -191
  41. package/src/router.js +0 -119
  42. package/src/view.js +0 -267
package/src/collection.js CHANGED
@@ -1,131 +1,172 @@
1
- // Backbone.js 1.4.0
2
- // (c) 2010-2019 Jeremy Ashkenas and DocumentCloud
3
- // Backbone may be freely distributed under the MIT license.
4
-
5
- // Collection
6
- // ----------
7
-
8
- // If models tend to represent a single row of data, a Collection is
9
- // more analogous to a table full of data ... or a small slice or page of that
10
- // table, or a collection of rows that belong together for a particular reason
11
- // -- all of the messages in this particular folder, all of the documents
12
- // belonging to this particular author, and so on. Collections maintain
13
- // indexes of their models, both in order, and for lookup by `id`.
14
-
15
- import { inherits, getResolveablePromise, getSyncMethod, wrapError } from './helpers.js';
16
- import { Events } from './events.js';
1
+ import { getResolveablePromise, getSyncMethod, wrapError } from './helpers.js';
17
2
  import { Model } from './model.js';
18
- import clone from "lodash-es/clone.js";
3
+ import clone from 'lodash-es/clone.js';
19
4
  import countBy from 'lodash-es/countBy.js';
20
- import difference from 'lodash-es/difference.js';
21
- import every from 'lodash-es/every.js';
22
- import extend from "lodash-es/extend.js";
23
- import findIndex from 'lodash-es/findIndex.js';
24
- import findLastIndex from 'lodash-es/findLastIndex.js';
25
5
  import groupBy from 'lodash-es/groupBy.js';
26
- import indexOf from 'lodash-es/indexOf.js';
27
- import isEmpty from "lodash-es/isEmpty.js";
28
- import isFunction from "lodash-es/isFunction.js";
6
+ import isFunction from 'lodash-es/isFunction.js';
29
7
  import isString from 'lodash-es/isString.js';
30
8
  import keyBy from 'lodash-es/keyBy.js';
31
- import lastIndexOf from 'lodash-es/lastIndexOf.js';
32
- import some from 'lodash-es/some.js';
33
9
  import sortBy from 'lodash-es/sortBy.js';
10
+ import EventEmitter from './eventemitter.js';
34
11
 
35
12
  const slice = Array.prototype.slice;
36
13
 
37
- // Create a new **Collection**, perhaps to contain a specific type of `model`.
38
- // If a `comparator` is specified, the Collection will maintain
39
- // its models in sort order, as they're added and removed.
40
- export const Collection = function(models, options) {
41
- options || (options = {});
42
- this.preinitialize.apply(this, arguments);
43
- if (options.model) this.model = options.model;
44
- if (options.comparator !== undefined) this.comparator = options.comparator;
45
- this._reset();
46
- this.initialize.apply(this, arguments);
47
- if (models) this.reset(models, extend({silent: true}, options));
48
- };
14
+ // Default options for `Collection#set`.
15
+ const setOptions = { add: true, remove: true, merge: true };
16
+ const addOptions = { add: true, remove: false };
17
+
18
+ /**
19
+ * @typedef {Record.<string, any>} Options
20
+ * @typedef {Record.<string, any>} Attributes
21
+ *
22
+ * @typedef {import('./storage.js').default} Storage
23
+ *
24
+ * @typedef {Record.<string, any>} CollectionOptions
25
+ * @property {Model} [model]
26
+ * @property {Function} [comparator]
27
+ */
28
+
29
+ /**
30
+ * If models tend to represent a single row of data, a Collection is
31
+ * more analogous to a table full of data ... or a small slice or page of that
32
+ * table, or a collection of rows that belong together for a particular reason
33
+ * -- all of the messages in this particular folder, all of the documents
34
+ * belonging to this particular author, and so on. Collections maintain
35
+ * indexes of their models, both in order, and for lookup by `id`.
36
+ */
37
+ class Collection extends EventEmitter(Object) {
38
+ /**
39
+ * Create a new **Collection**, perhaps to contain a specific type of `model`.
40
+ * If a `comparator` is specified, the Collection will maintain
41
+ * its models in sort order, as they're added and removed.
42
+ * @param {Model[]} [models]
43
+ * @param {CollectionOptions} [options]
44
+ */
45
+ constructor(models, options) {
46
+ super();
47
+ options || (options = {});
48
+ this.preinitialize.apply(this, arguments);
49
+ if (options.model) this._model = options.model;
50
+ if (options.comparator !== undefined) this.comparator = options.comparator;
51
+ this._reset();
52
+ this.initialize.apply(this, arguments);
53
+ if (models) this.reset(models, Object.assign({ silent: true }, options));
54
+
55
+ this[Symbol.iterator] = this.values;
56
+ }
49
57
 
50
- Collection.extend = inherits;
58
+ /**
59
+ * @param {Storage} storage
60
+ */
61
+ set browserStorage(storage) {
62
+ this._browserStorage = storage;
63
+ }
51
64
 
65
+ /**
66
+ * @returns {Storage} storage
67
+ */
68
+ get browserStorage() {
69
+ return this._browserStorage;
70
+ }
52
71
 
53
- // Default options for `Collection#set`.
54
- const setOptions = {add: true, remove: true, merge: true};
55
- const addOptions = {add: true, remove: false};
56
-
57
- // Splices `insert` into `array` at index `at`.
58
- const splice = function(array, insert, at) {
59
- at = Math.min(Math.max(at, 0), array.length);
60
- const tail = Array(array.length - at);
61
- const length = insert.length;
62
- let i;
63
- for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
64
- for (i = 0; i < length; i++) array[i + at] = insert[i];
65
- for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
66
- };
67
-
68
- // Define the Collection's inheritable methods.
69
- Object.assign(Collection.prototype, Events, {
70
-
71
- // The default model for a collection is just a **Backbone.Model**.
72
- // This should be overridden in most cases.
73
- model: Model,
74
-
75
-
76
- // preinitialize is an empty function by default. You can override it with a function
77
- // or object. preinitialize will run before any instantiation logic is run in the Collection.
78
- preinitialize: function(){},
79
-
80
- // Initialize is an empty function by default. Override it with your own
81
- // initialization logic.
82
- initialize: function(){},
83
-
84
- // The JSON representation of a Collection is an array of the
85
- // models' attributes.
86
- toJSON: function(options) {
87
- return this.map(function(model) { return model.toJSON(options); });
88
- },
89
-
90
- // Proxy `Backbone.sync` by default.
91
- sync: function(method, model, options) {
72
+ /**
73
+ * The default model for a collection is just a **Model**.
74
+ * This should be overridden in most cases.
75
+ * @returns {typeof Model}
76
+ */
77
+ get model() {
78
+ return this._model ?? Model;
79
+ }
80
+
81
+ /**
82
+ * @param {Model} model
83
+ */
84
+ set model(model) {
85
+ this._model = model;
86
+ }
87
+
88
+ get length() {
89
+ return this.models.length;
90
+ }
91
+
92
+ /**
93
+ * preinitialize is an empty function by default. You can override it with a function
94
+ * or object. preinitialize will run before any instantiation logic is run in the Collection.
95
+ */
96
+ preinitialize() {}
97
+
98
+ /**
99
+ * Initialize is an empty function by default. Override it with your own
100
+ * initialization logic.
101
+ */
102
+ initialize() {}
103
+
104
+ /**
105
+ * The JSON representation of a Collection is an array of the
106
+ * models' attributes.
107
+ *@param {Options} options
108
+ */
109
+ toJSON(options) {
110
+ return this.map(function (model) {
111
+ return model.toJSON(options);
112
+ });
113
+ }
114
+
115
+ /**
116
+ *@param {string} method
117
+ *@param {Model|Collection} model
118
+ *@param {Options} options
119
+ */
120
+ sync(method, model, options) {
92
121
  return getSyncMethod(this)(method, model, options);
93
- },
94
-
95
- // Add a model, or list of models to the set. `models` may be Backbone
96
- // Models or raw JavaScript objects to be converted to Models, or any
97
- // combination of the two.
98
- add: function(models, options) {
99
- return this.set(models, extend({merge: false}, options, addOptions));
100
- },
101
-
102
- // Remove a model, or a list of models from the set.
103
- remove: function(models, options) {
104
- options = extend({}, options);
122
+ }
123
+
124
+ /**
125
+ * Add a model, or list of models to the set. `models` may be
126
+ * Models or raw JavaScript objects to be converted to Models, or any
127
+ * combination of the two.
128
+ *@param {Model[]|Model|Attributes|Attributes[]} models
129
+ *@param {Options} options
130
+ */
131
+ add(models, options) {
132
+ return this.set(models, Object.assign({ merge: false }, options, addOptions));
133
+ }
134
+
135
+ /**
136
+ * Remove a model, or a list of models from the set.
137
+ * @param {Model|Model[]} models
138
+ * @param {Options} options
139
+ */
140
+ remove(models, options) {
141
+ options = Object.assign({}, options);
105
142
  const singular = !Array.isArray(models);
106
- models = singular ? [models] : models.slice();
107
- const removed = this._removeModels(models, options);
143
+ const modelsArray = singular ? [models] : /** @type {Model[]} */ (models).slice();
144
+ const removed = this._removeModels(modelsArray, options);
108
145
  if (!options.silent && removed.length) {
109
- options.changes = {added: [], merged: [], removed: removed};
146
+ options.changes = { added: [], merged: [], removed: removed };
110
147
  this.trigger('update', this, options);
111
148
  }
112
149
  return singular ? removed[0] : removed;
113
- },
150
+ }
114
151
 
115
- // Update a collection by `set`-ing a new list of models, adding new ones,
116
- // removing models that are no longer present, and merging models that
117
- // already exist in the collection, as necessary. Similar to **Model#set**,
118
- // the core operation for updating the data contained by the collection.
119
- set: function(models, options) {
152
+ /**
153
+ * Update a collection by `set`-ing a new list of models, adding new ones,
154
+ * removing models that are no longer present, and merging models that
155
+ * already exist in the collection, as necessary. Similar to **Model#set**,
156
+ * the core operation for updating the data contained by the collection.
157
+ *@param {Model[]|Model|Attributes|Attributes[]} models
158
+ * @param {Options} options
159
+ */
160
+ set(models, options) {
120
161
  if (models == null) return;
121
162
 
122
- options = extend({}, setOptions, options);
163
+ options = Object.assign({}, setOptions, options);
123
164
  if (options.parse && !this._isModel(models)) {
124
165
  models = this.parse(models, options) || [];
125
166
  }
126
167
 
127
168
  const singular = !Array.isArray(models);
128
- models = singular ? [models] : models.slice();
169
+ models = singular ? [/** @type {Model} */ (models)] : /** @type {Model[]} */ (models).slice();
129
170
 
130
171
  let at = options.at;
131
172
  if (at != null) at = +at;
@@ -169,7 +210,7 @@ Object.assign(Collection.prototype, Events, {
169
210
  }
170
211
  models[i] = existing;
171
212
 
172
- // If this is a new, valid model, push it to the `toAdd` list.
213
+ // If this is a new, valid model, push it to the `toAdd` list.
173
214
  } else if (add) {
174
215
  model = models[i] = this._prepareModel(model, options);
175
216
  if (model) {
@@ -193,19 +234,20 @@ Object.assign(Collection.prototype, Events, {
193
234
  // See if sorting is needed, update `length` and splice in new models.
194
235
  let orderChanged = false;
195
236
  const replace = !sortable && add && remove;
237
+
196
238
  if (set.length && replace) {
197
- orderChanged = this.length !== set.length || some(this.models, (m, index) => m !== set[index]);
239
+ orderChanged = this.length !== set.length || this.models.some((m, idx) => m !== set[idx]);
198
240
  this.models.length = 0;
199
- splice(this.models, set, 0);
200
- this.length = this.models.length;
241
+ this.models.splice(0, 0, ...set);
201
242
  } else if (toAdd.length) {
202
243
  if (sortable) sort = true;
203
- splice(this.models, toAdd, at == null ? this.length : at);
204
- this.length = this.models.length;
244
+ let idx = at == null ? this.length : at;
245
+ idx = Math.min(Math.max(idx, 0), this.models.length);
246
+ this.models.splice(idx, 0, ...toAdd);
205
247
  }
206
248
 
207
249
  // Silently sort the collection if appropriate.
208
- if (sort) this.sort({silent: true});
250
+ if (sort) this.sort({ silent: true });
209
251
 
210
252
  // Unless silenced, it's time to fire all appropriate add/sort/update events.
211
253
  if (!options.silent) {
@@ -219,7 +261,7 @@ Object.assign(Collection.prototype, Events, {
219
261
  options.changes = {
220
262
  added: toAdd,
221
263
  removed: toRemove,
222
- merged: toMerge
264
+ merged: toMerge,
223
265
  };
224
266
  this.trigger('update', this, options);
225
267
  }
@@ -227,242 +269,304 @@ Object.assign(Collection.prototype, Events, {
227
269
 
228
270
  // Return the added (or merged) model (or models).
229
271
  return singular ? models[0] : models;
230
- },
231
-
232
- clearStore: async function(options={}, filter=(o) => o) {
233
- await Promise.all(this.models
234
- .filter(filter)
235
- .map(m => {
236
- return new Promise(
237
- resolve => {
238
- m.destroy(Object.assign(options, {
239
- 'success': resolve,
240
- 'error': (m, e) => { console.error(e); resolve() }
241
- }));
242
- }
243
- );
244
- })
245
- );
246
- await this.browserStorage.clear();
247
- this.reset();
248
- },
249
-
250
- // When you have more items than you want to add or remove individually,
251
- // you can reset the entire set with a new list of models, without firing
252
- // any granular `add` or `remove` events. Fires `reset` when finished.
253
- // Useful for bulk operations and optimizations.
254
- reset: function(models, options) {
272
+ }
273
+
274
+ async clearStore(options = {}, filter = (o) => o) {
275
+ await Promise.all(
276
+ this.models.filter(filter).map((m) => {
277
+ return new Promise((resolve) => {
278
+ m.destroy(
279
+ Object.assign(options, {
280
+ 'success': resolve,
281
+ 'error': (m, e) => {
282
+ console.error(e);
283
+ resolve();
284
+ },
285
+ }),
286
+ );
287
+ });
288
+ }),
289
+ );
290
+ await this.browserStorage.clear();
291
+ this.reset();
292
+ }
293
+
294
+ /**
295
+ * When you have more items than you want to add or remove individually,
296
+ * you can reset the entire set with a new list of models, without firing
297
+ * any granular `add` or `remove` events. Fires `reset` when finished.
298
+ * Useful for bulk operations and optimizations.
299
+ * @param {Model|Model[]} [models]
300
+ * @param {Options} [options]
301
+ */
302
+ reset(models, options) {
255
303
  options = options ? clone(options) : {};
256
304
  for (let i = 0; i < this.models.length; i++) {
257
305
  this._removeReference(this.models[i], options);
258
306
  }
259
307
  options.previousModels = this.models;
260
308
  this._reset();
261
- models = this.add(models, extend({silent: true}, options));
309
+ models = this.add(models, Object.assign({ silent: true }, options));
262
310
  if (!options.silent) this.trigger('reset', this, options);
263
311
  return models;
264
- },
312
+ }
265
313
 
266
- // Add a model to the end of the collection.
267
- push: function(model, options) {
268
- return this.add(model, extend({at: this.length}, options));
269
- },
314
+ /**
315
+ * Add a model to the end of the collection.
316
+ * @param {Model} model
317
+ * @param {Options} [options]
318
+ */
319
+ push(model, options) {
320
+ return this.add(model, Object.assign({ at: this.length }, options));
321
+ }
270
322
 
271
- // Remove a model from the end of the collection.
272
- pop: function(options) {
323
+ /**
324
+ * Remove a model from the end of the collection.
325
+ * @param {Options} [options]
326
+ */
327
+ pop(options) {
273
328
  const model = this.at(this.length - 1);
274
329
  return this.remove(model, options);
275
- },
330
+ }
276
331
 
277
- // Add a model to the beginning of the collection.
278
- unshift: function(model, options) {
279
- return this.add(model, extend({at: 0}, options));
280
- },
332
+ /**
333
+ * Add a model to the beginning of the collection.
334
+ * @param {Model} model
335
+ * @param {Options} [options]
336
+ */
337
+ unshift(model, options) {
338
+ return this.add(model, Object.assign({ at: 0 }, options));
339
+ }
281
340
 
282
- // Remove a model from the beginning of the collection.
283
- shift: function(options) {
341
+ /**
342
+ * Remove a model from the beginning of the collection.
343
+ * @param {Options} [options]
344
+ */
345
+ shift(options) {
284
346
  const model = this.at(0);
285
347
  return this.remove(model, options);
286
- },
348
+ }
287
349
 
288
- // Slice out a sub-array of models from the collection.
289
- slice: function() {
350
+ /** Slice out a sub-array of models from the collection. */
351
+ slice() {
290
352
  return slice.apply(this.models, arguments);
291
- },
353
+ }
292
354
 
293
- filter: function(callback, thisArg) {
294
- return this.models.filter(
295
- isFunction(callback) ? callback : m => m.matches(callback),
296
- thisArg
297
- );
298
- },
355
+ /**
356
+ * @param {Function|Object} callback
357
+ * @param {any} thisArg
358
+ */
359
+ filter(callback, thisArg) {
360
+ return this.models.filter(isFunction(callback) ? callback : (m) => m.matches(callback), thisArg);
361
+ }
299
362
 
300
- every: function(pred) {
301
- return every(this.models.map(m => m.attributes), pred);
302
- },
363
+ /**
364
+ * @param {Function} pred
365
+ */
366
+ every(pred) {
367
+ if (isFunction(pred)) {
368
+ return this.models.map((m) => m.attributes).every(pred);
369
+ } else {
370
+ return this.models.every((m) => m.matches(pred));
371
+ }
372
+ }
303
373
 
304
- difference: function(values) {
305
- return difference(this.models, values);
306
- },
374
+ /**
375
+ * @param {Model[]} values
376
+ */
377
+ difference(values) {
378
+ return this.models.filter((m) => !values.includes(m));
379
+ }
307
380
 
308
- max: function() {
381
+ max() {
309
382
  return Math.max.apply(Math, this.models);
310
- },
383
+ }
311
384
 
312
- min: function() {
385
+ min() {
313
386
  return Math.min.apply(Math, this.models);
314
- },
387
+ }
315
388
 
316
- drop: function(n=1) {
389
+ drop(n = 1) {
317
390
  return this.models.slice(n);
318
- },
391
+ }
319
392
 
320
- some: function(pred) {
321
- return some(this.models.map(m => m.attributes), pred);
322
- },
393
+ /**
394
+ * @param {Function|Object} pred
395
+ */
396
+ some(pred) {
397
+ if (isFunction(pred)) {
398
+ return this.models.map((m) => m.attributes).some(pred);
399
+ } else {
400
+ return this.models.some((m) => m.matches(pred));
401
+ }
402
+ }
323
403
 
324
- sortBy: function(iteratee) {
404
+ sortBy(iteratee) {
325
405
  return sortBy(
326
406
  this.models,
327
- isFunction(iteratee) ? iteratee : m => isString(iteratee) ? m.get(iteratee) : m.matches(iteratee),
407
+ isFunction(iteratee) ? iteratee : (m) => (isString(iteratee) ? m.get(iteratee) : m.matches(iteratee)),
328
408
  );
329
- },
409
+ }
330
410
 
331
- isEmpty: function() {
332
- return isEmpty(this.models);
333
- },
411
+ isEmpty() {
412
+ return !this.models.length;
413
+ }
334
414
 
335
- keyBy: function(iteratee) {
415
+ keyBy(iteratee) {
336
416
  return keyBy(this.models, iteratee);
337
- },
417
+ }
338
418
 
339
- each: function(callback, thisArg) {
419
+ each(callback, thisArg) {
340
420
  return this.forEach(callback, thisArg);
341
- },
421
+ }
342
422
 
343
- forEach: function(callback, thisArg) {
423
+ forEach(callback, thisArg) {
344
424
  return this.models.forEach(callback, thisArg);
345
- },
425
+ }
346
426
 
347
- includes: function(item) {
427
+ includes(item) {
348
428
  return this.models.includes(item);
349
- },
429
+ }
350
430
 
351
- size: function() {
431
+ size() {
352
432
  return this.models.length;
353
- },
433
+ }
354
434
 
355
- countBy: function(f) {
356
- return countBy(
357
- this.models,
358
- isFunction(f) ? f : m => isString(f) ? m.get(f) : m.matches(f),
359
- );
360
- },
435
+ countBy(f) {
436
+ return countBy(this.models, isFunction(f) ? f : (m) => (isString(f) ? m.get(f) : m.matches(f)));
437
+ }
361
438
 
362
- groupBy: function(pred) {
363
- return groupBy(
364
- this.models,
365
- isFunction(pred) ? pred : m => isString(pred) ? m.get(pred) : m.matches(pred),
366
- );
367
- },
439
+ groupBy(pred) {
440
+ return groupBy(this.models, isFunction(pred) ? pred : (m) => (isString(pred) ? m.get(pred) : m.matches(pred)));
441
+ }
368
442
 
369
- indexOf: function(fromIndex) {
370
- return indexOf(this.models, fromIndex);
371
- },
443
+ /**
444
+ * @param {number} fromIndex
445
+ */
446
+ indexOf(fromIndex) {
447
+ return this.models.indexOf(fromIndex);
448
+ }
372
449
 
373
- findLastIndex: function(pred, fromIndex) {
374
- return findLastIndex(
375
- this.models,
376
- isFunction(pred) ? pred : m => isString(pred) ? m.get(pred) : m.matches(pred),
377
- fromIndex
450
+ /**
451
+ * @param {Function|string|RegExp} pred
452
+ * @param {number} fromIndex
453
+ */
454
+ findLastIndex(pred, fromIndex) {
455
+ return this.models.findLastIndex(
456
+ isFunction(pred) ? pred : (m) => (isString(pred) ? m.get(pred) : m.matches(pred)),
457
+ fromIndex,
378
458
  );
379
- },
459
+ }
380
460
 
381
- lastIndexOf: function(fromIndex) {
382
- return lastIndexOf(this.models, fromIndex);
383
- },
461
+ /**
462
+ * @param {number} fromIndex
463
+ */
464
+ lastIndexOf(fromIndex) {
465
+ return this.models.lastIndexOf(fromIndex);
466
+ }
384
467
 
385
- findIndex: function(pred) {
386
- return findIndex(
387
- this.models,
388
- isFunction(pred) ? pred : m => isString(pred) ? m.get(pred) : m.matches(pred),
389
- );
390
- },
468
+ /**
469
+ * @param {Function|string|RegExp} pred
470
+ */
471
+ findIndex(pred) {
472
+ return this.models.findIndex(isFunction(pred) ? pred : (m) => (isString(pred) ? m.get(pred) : m.matches(pred)));
473
+ }
391
474
 
392
- last: function() {
475
+ last() {
393
476
  const length = this.models == null ? 0 : this.models.length;
394
477
  return length ? this.models[length - 1] : undefined;
395
- },
478
+ }
396
479
 
397
- head: function() {
480
+ head() {
398
481
  return this.models[0];
399
- },
482
+ }
400
483
 
401
- first: function() {
484
+ first() {
402
485
  return this.head();
403
- },
486
+ }
404
487
 
405
- map: function(cb, thisArg) {
406
- return this.models.map(
407
- isFunction(cb) ? cb : m => isString(cb) ? m.get(cb) : m.matches(cb),
408
- thisArg
409
- );
410
- },
488
+ map(cb, thisArg) {
489
+ return this.models.map(isFunction(cb) ? cb : (m) => (isString(cb) ? m.get(cb) : m.matches(cb)), thisArg);
490
+ }
411
491
 
412
- reduce: function(callback, initialValue) {
492
+ reduce(callback, initialValue) {
413
493
  return this.models.reduce(callback, initialValue || this.models[0]);
414
- },
494
+ }
415
495
 
416
- reduceRight: function(callback, initialValue) {
496
+ reduceRight(callback, initialValue) {
417
497
  return this.models.reduceRight(callback, initialValue || this.models[0]);
418
- },
498
+ }
419
499
 
420
- toArray: function() {
500
+ toArray() {
421
501
  return Array.from(this.models);
422
- },
502
+ }
423
503
 
424
- // Get a model from the set by id, cid, model object with id or cid
425
- // properties, or an attributes object that is transformed through modelId.
426
- get: function(obj) {
504
+ /**
505
+ * Get a model from the set by id, cid, model object with id or cid
506
+ * properties, or an attributes object that is transformed through modelId.
507
+ * @param {string|number|Object|Model} obj
508
+ */
509
+ get(obj) {
427
510
  if (obj == null) return undefined;
428
- return this._byId[obj] ||
511
+ return (
512
+ this._byId[obj] ||
429
513
  this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj)] ||
430
- obj.cid && this._byId[obj.cid];
431
- },
514
+ (obj.cid && this._byId[obj.cid])
515
+ );
516
+ }
432
517
 
433
- // Returns `true` if the model is in the collection.
434
- has: function(obj) {
518
+ /**
519
+ * Returns `true` if the model is in the collection.
520
+ * @param {string|number|Object|Model} obj
521
+ */
522
+ has(obj) {
435
523
  return this.get(obj) != null;
436
- },
524
+ }
437
525
 
438
- // Get the model at the given index.
439
- at: function(index) {
526
+ /**
527
+ * Get the model at the given index.
528
+ * @param {number} index
529
+ */
530
+ at(index) {
440
531
  if (index < 0) index += this.length;
441
532
  return this.models[index];
442
- },
533
+ }
443
534
 
444
- // Return models with matching attributes. Useful for simple cases of
445
- // `filter`.
446
- where: function(attrs, first) {
535
+ /**
536
+ * Return models with matching attributes. Useful for simple cases of
537
+ * `filter`.
538
+ * @param {Attributes} attrs
539
+ * @param {boolean} [first]
540
+ */
541
+ where(attrs, first) {
447
542
  return this[first ? 'find' : 'filter'](attrs);
448
- },
543
+ }
449
544
 
450
- // Return the first model with matching attributes. Useful for simple cases
451
- // of `find`.
452
- findWhere: function(attrs) {
545
+ /**
546
+ * Return the first model with matching attributes. Useful for simple cases
547
+ * of `find`.
548
+ * @param {Attributes} attrs
549
+ */
550
+ findWhere(attrs) {
453
551
  return this.where(attrs, true);
454
- },
552
+ }
455
553
 
456
- find: function(predicate, fromIndex) {
457
- const pred = isFunction(predicate) ? predicate : m => m.matches(predicate);
554
+ /**
555
+ * @param {Attributes} predicate
556
+ * @param {number} [fromIndex]
557
+ */
558
+ find(predicate, fromIndex) {
559
+ const pred = isFunction(predicate) ? predicate : (m) => m.matches(predicate);
458
560
  return this.models.find(pred, fromIndex);
459
- },
460
-
561
+ }
461
562
 
462
- // Force the collection to re-sort itself. You don't need to call this under
463
- // normal circumstances, as the set will maintain sort order as each item
464
- // is added.
465
- sort: function(options) {
563
+ /**
564
+ * Force the collection to re-sort itself. You don't need to call this under
565
+ * normal circumstances, as the set will maintain sort order as each item
566
+ * is added.
567
+ * @param {Options} [options]
568
+ */
569
+ sort(options) {
466
570
  let comparator = this.comparator;
467
571
  if (!comparator) throw new Error('Cannot sort a set without a comparator');
468
572
  options || (options = {});
@@ -478,22 +582,29 @@ Object.assign(Collection.prototype, Events, {
478
582
  }
479
583
  if (!options.silent) this.trigger('sort', this, options);
480
584
  return this;
481
- },
585
+ }
482
586
 
483
- // Pluck an attribute from each model in the collection.
484
- pluck: function(attr) {
587
+ /**
588
+ * Pluck an attribute from each model in the collection.
589
+ * @param {string} attr
590
+ */
591
+ pluck(attr) {
485
592
  return this.map(attr + '');
486
- },
593
+ }
487
594
 
488
- // Fetch the default set of models for this collection, resetting the
489
- // collection when they arrive. If `reset: true` is passed, the response
490
- // data will be passed through the `reset` method instead of `set`.
491
- fetch: function(options) {
492
- options = extend({parse: true}, options);
595
+ /**
596
+ * Fetch the default set of models for this collection, resetting the
597
+ * collection when they arrive. If `reset: true` is passed, the response
598
+ * data will be passed through the `reset` method instead of `set`.
599
+ * @param {Options} options
600
+ */
601
+ fetch(options) {
602
+ options = Object.assign({ parse: true }, options);
493
603
  const success = options.success;
604
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
494
605
  const collection = this;
495
606
  const promise = options.promise && getResolveablePromise();
496
- options.success = function(resp) {
607
+ options.success = function (resp) {
497
608
  const method = options.reset ? 'reset' : 'set';
498
609
  collection[method](resp, options);
499
610
  if (success) success.call(options.context, collection, resp, options);
@@ -502,12 +613,16 @@ Object.assign(Collection.prototype, Events, {
502
613
  };
503
614
  wrapError(this, options);
504
615
  return promise ? promise : this.sync('read', this, options);
505
- },
616
+ }
506
617
 
507
- // Create a new instance of a model in this collection. Add the model to the
508
- // collection immediately, unless `wait: true` is passed, in which case we
509
- // wait for the server to agree.
510
- create: function(model, options) {
618
+ /**
619
+ * Create a new instance of a model in this collection. Add the model to the
620
+ * collection immediately, unless `wait: true` is passed, in which case we
621
+ * wait for the server to agree.
622
+ * @param {Model|Attributes} model
623
+ * @param {Options} [options]
624
+ */
625
+ create(model, options) {
511
626
  options = options ? clone(options) : {};
512
627
  const wait = options.wait;
513
628
  const return_promise = options.promise;
@@ -516,10 +631,11 @@ Object.assign(Collection.prototype, Events, {
516
631
  model = this._prepareModel(model, options);
517
632
  if (!model) return false;
518
633
  if (!wait) this.add(model, options);
634
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
519
635
  const collection = this;
520
636
  const success = options.success;
521
637
  const error = options.error;
522
- options.success = function(m, resp, callbackOpts) {
638
+ options.success = function (m, resp, callbackOpts) {
523
639
  if (wait) {
524
640
  collection.add(m, callbackOpts);
525
641
  }
@@ -530,78 +646,96 @@ Object.assign(Collection.prototype, Events, {
530
646
  promise.resolve(m);
531
647
  }
532
648
  };
533
- options.error = function(model, e, options) {
649
+ options.error = function (model, e, options) {
534
650
  error && error.call(options.context, model, e, options);
535
651
  return_promise && promise.reject(e);
536
- }
652
+ };
537
653
 
538
- model.save(null, Object.assign(options, {'promise': false}));
654
+ model.save(null, Object.assign(options, { 'promise': false }));
539
655
  if (return_promise) {
540
656
  return promise;
541
657
  } else {
542
658
  return model;
543
659
  }
544
- },
660
+ }
545
661
 
546
- // **parse** converts a response into a list of models to be added to the
547
- // collection. The default implementation is just to pass it through.
548
- parse: function(resp, options) {
662
+ /**
663
+ * **parse** converts a response into a list of models to be added to the
664
+ * collection. The default implementation is just to pass it through.
665
+ * @param {Object} resp
666
+ * @param {Options} [options]
667
+ */
668
+ parse(resp, options) {
549
669
  return resp;
550
- },
551
-
552
- // Create a new collection with an identical list of models as this one.
553
- clone: function() {
554
- return new this.constructor(this.models, {
555
- model: this.model,
556
- comparator: this.comparator
557
- });
558
- },
670
+ }
559
671
 
560
- // Define how to uniquely identify models in the collection.
561
- modelId: function(attrs) {
672
+ /**
673
+ * Define how to uniquely identify models in the collection.
674
+ * @param {Attributes} attrs
675
+ */
676
+ modelId(attrs) {
562
677
  return attrs[this.model.prototype?.idAttribute || 'id'];
563
- },
678
+ }
564
679
 
565
- // Get an iterator of all models in this collection.
566
- values: function() {
680
+ /** Get an iterator of all models in this collection. */
681
+ values() {
567
682
  return new CollectionIterator(this, ITERATOR_VALUES);
568
- },
683
+ }
569
684
 
570
- // Get an iterator of all model IDs in this collection.
571
- keys: function() {
685
+ /** Get an iterator of all model IDs in this collection. */
686
+ keys() {
572
687
  return new CollectionIterator(this, ITERATOR_KEYS);
573
- },
688
+ }
574
689
 
575
- // Get an iterator of all [ID, model] tuples in this collection.
576
- entries: function() {
690
+ /** Get an iterator of all [ID, model] tuples in this collection. */
691
+ entries() {
577
692
  return new CollectionIterator(this, ITERATOR_KEYSVALUES);
578
- },
693
+ }
579
694
 
580
- // Private method to reset all internal state. Called when the collection
581
- // is first initialized or reset.
582
- _reset: function() {
583
- this.length = 0;
695
+ /**
696
+ * Private method to reset all internal state. Called when the collection
697
+ * is first initialized or reset.
698
+ */
699
+ _reset() {
584
700
  this.models = [];
585
- this._byId = {};
586
- },
701
+ this._byId = {};
702
+ }
703
+
704
+ /**
705
+ * @param {Attributes} attrs
706
+ * @param {Options} [options]
707
+ */
708
+ createModel(attrs, options) {
709
+ const Klass = this.model;
710
+ return new Klass(attrs, options);
711
+ }
587
712
 
588
- // Prepare a hash of attributes (or other model) to be added to this
589
- // collection.
590
- _prepareModel: function(attrs, options) {
713
+ /**
714
+ * Prepare a hash of attributes (or other model) to be added to this
715
+ * collection.
716
+ * @param {Attributes|Model} attrs
717
+ * @param {Options} [options]
718
+ * @return {Model}
719
+ */
720
+ _prepareModel(attrs, options) {
591
721
  if (this._isModel(attrs)) {
592
722
  if (!attrs.collection) attrs.collection = this;
593
- return attrs;
723
+ return /** @type {Model} */ (attrs);
594
724
  }
595
725
  options = options ? clone(options) : {};
596
726
  options.collection = this;
597
- const model = new this.model(attrs, options);
727
+ const model = this.createModel(attrs, options);
598
728
  if (!model.validationError) return model;
599
729
  this.trigger('invalid', this, model.validationError, options);
600
- return false;
601
- },
730
+ return null;
731
+ }
602
732
 
603
- // Internal method called by both remove and set.
604
- _removeModels: function(models, options) {
733
+ /**
734
+ * Internal method called by both remove and set.
735
+ * @param {Model[]} models
736
+ * @param {Options} [options]
737
+ */
738
+ _removeModels(models, options) {
605
739
  const removed = [];
606
740
  for (let i = 0; i < models.length; i++) {
607
741
  const model = this.get(models[i]);
@@ -609,7 +743,6 @@ Object.assign(Collection.prototype, Events, {
609
743
 
610
744
  const index = this.indexOf(model);
611
745
  this.models.splice(index, 1);
612
- this.length--;
613
746
 
614
747
  // Remove references before triggering 'remove' event to prevent an
615
748
  // infinite loop. #3693
@@ -626,36 +759,55 @@ Object.assign(Collection.prototype, Events, {
626
759
  this._removeReference(model, options);
627
760
  }
628
761
  return removed;
629
- },
762
+ }
630
763
 
631
- // Method for checking whether an object should be considered a model for
632
- // the purposes of adding to the collection.
633
- _isModel: function(model) {
764
+ /**
765
+ * Method for checking whether an object should be considered a model for
766
+ * the purposes of adding to the collection.
767
+ * @param {any} model
768
+ */
769
+ _isModel(model) {
634
770
  return model instanceof Model;
635
- },
771
+ }
636
772
 
637
- // Internal method to create a model's ties to a collection.
638
- _addReference: function(model, options) {
773
+ /**
774
+ * Internal method to create a model's ties to a collection.
775
+ * @param {Model} model
776
+ * @param {Options} [options]
777
+ */
778
+ _addReference(model, options) {
639
779
  this._byId[model.cid] = model;
640
780
  const id = this.modelId(model.attributes);
641
781
  if (id != null) this._byId[id] = model;
642
782
  model.on('all', this._onModelEvent, this);
643
- },
783
+ }
644
784
 
645
- // Internal method to sever a model's ties to a collection.
646
- _removeReference: function(model, options) {
785
+ /**
786
+ * Internal method to sever a model's ties to a collection.
787
+ * @private
788
+ * @param {Model} model
789
+ * @param {Options} [options]
790
+ */
791
+ _removeReference(model, options) {
647
792
  delete this._byId[model.cid];
648
793
  const id = this.modelId(model.attributes);
649
794
  if (id != null) delete this._byId[id];
650
795
  if (this === model.collection) delete model.collection;
651
796
  model.off('all', this._onModelEvent, this);
652
- },
797
+ }
653
798
 
654
- // Internal method called every time a model in the set fires an event.
655
- // Sets need to update their indexes when models change ids. All other
656
- // events simply proxy through. "add" and "remove" events that originate
657
- // in other collections are ignored.
658
- _onModelEvent: function(event, model, collection, options) {
799
+ /**
800
+ * Internal method called every time a model in the set fires an event.
801
+ * Sets need to update their indexes when models change ids. All other
802
+ * events simply proxy through. "add" and "remove" events that originate
803
+ * in other collections are ignored.
804
+ * @private
805
+ * @param {any} event
806
+ * @param {Model} model
807
+ * @param {Collection} collection
808
+ * @param {Options} [options]
809
+ */
810
+ _onModelEvent(event, model, collection, options) {
659
811
  if (model) {
660
812
  if ((event === 'add' || event === 'remove') && collection !== this) return;
661
813
  if (event === 'destroy') this.remove(model, options);
@@ -670,30 +822,8 @@ Object.assign(Collection.prototype, Events, {
670
822
  }
671
823
  this.trigger.apply(this, arguments);
672
824
  }
673
-
674
- });
675
-
676
- // Defining an @@iterator method implements JavaScript's Iterable protocol.
677
- // In modern ES2015 browsers, this value is found at Symbol.iterator.
678
- /* global Symbol */
679
- const $$iterator = typeof Symbol === 'function' && Symbol.iterator;
680
- if ($$iterator) {
681
- Collection.prototype[$$iterator] = Collection.prototype.values;
682
825
  }
683
826
 
684
- // CollectionIterator
685
- // ------------------
686
-
687
- // A CollectionIterator implements JavaScript's Iterator protocol, allowing the
688
- // use of `for of` loops in modern browsers and interoperation between
689
- // Collection and other JavaScript functions and third-party libraries
690
- // which can operate on Iterables.
691
- const CollectionIterator = function(collection, kind) {
692
- this._collection = collection;
693
- this._kind = kind;
694
- this._index = 0;
695
- };
696
-
697
827
  // This "enum" defines the three possible kinds of values which can be emitted
698
828
  // by a CollectionIterator that correspond to the values(), keys() and entries()
699
829
  // methods on Collection, respectively.
@@ -701,40 +831,55 @@ const ITERATOR_VALUES = 1;
701
831
  const ITERATOR_KEYS = 2;
702
832
  const ITERATOR_KEYSVALUES = 3;
703
833
 
704
- // All Iterators should themselves be Iterable.
705
- if ($$iterator) {
706
- CollectionIterator.prototype[$$iterator] = function() {
707
- return this;
708
- };
709
- }
834
+ class CollectionIterator {
835
+ /**
836
+ * A CollectionIterator implements JavaScript's Iterator protocol, allowing the
837
+ * use of `for of` loops in modern browsers and interoperation between
838
+ * Collection and other JavaScript functions and third-party libraries
839
+ * which can operate on Iterables.
840
+ * @param {Collection} collection
841
+ * @param {Number} kind
842
+ */
843
+ constructor(collection, kind) {
844
+ this._collection = collection;
845
+ this._kind = kind;
846
+ this._index = 0;
847
+ }
710
848
 
711
- CollectionIterator.prototype.next = function() {
712
- if (this._collection) {
713
-
714
- // Only continue iterating if the iterated collection is long enough.
715
- if (this._index < this._collection.length) {
716
- const model = this._collection.at(this._index);
717
- this._index++;
718
-
719
- // Construct a value depending on what kind of values should be iterated.
720
- let value;
721
- if (this._kind === ITERATOR_VALUES) {
722
- value = model;
723
- } else {
724
- const id = this._collection.modelId(model.attributes);
725
- if (this._kind === ITERATOR_KEYS) {
726
- value = id;
727
- } else { // ITERATOR_KEYSVALUES
728
- value = [id, model];
849
+ next() {
850
+ if (this._collection) {
851
+ // Only continue iterating if the iterated collection is long enough.
852
+ if (this._index < this._collection.length) {
853
+ const model = this._collection.at(this._index);
854
+ this._index++;
855
+
856
+ // Construct a value depending on what kind of values should be iterated.
857
+ let value;
858
+ if (this._kind === ITERATOR_VALUES) {
859
+ value = model;
860
+ } else {
861
+ const id = this._collection.modelId(model.attributes);
862
+ if (this._kind === ITERATOR_KEYS) {
863
+ value = id;
864
+ } else {
865
+ // ITERATOR_KEYSVALUES
866
+ value = [id, model];
867
+ }
729
868
  }
869
+ return { value: value, done: false };
730
870
  }
731
- return {value: value, done: false};
871
+
872
+ // Once exhausted, remove the reference to the collection so future
873
+ // calls to the next method always return done.
874
+ this._collection = undefined;
732
875
  }
733
876
 
734
- // Once exhausted, remove the reference to the collection so future
735
- // calls to the next method always return done.
736
- this._collection = undefined;
877
+ return { value: undefined, done: true };
737
878
  }
738
879
 
739
- return {value: undefined, done: true};
740
- };
880
+ [Symbol.iterator]() {
881
+ return this;
882
+ }
883
+ }
884
+
885
+ export { Collection };