From a95b018a682e6e36e8fef5031e123b2071b2da57 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 26 Oct 2023 11:40:07 +0200 Subject: [PATCH 1/3] Make the EventEmitter a mixin class... that can extend any other class. Also, remove the old Events constructor. --- src/collection.js | 2 +- src/element.js | 24 +- src/eventemitter.js | 320 +++++++++--------- src/events.js | 328 ------------------- src/index.js | 6 +- src/listening.js | 19 +- src/model.js | 2 +- src/utils/events.js | 2 +- test/events.js | 770 +++++++++++++++++++++++++------------------- 9 files changed, 621 insertions(+), 852 deletions(-) delete mode 100644 src/events.js diff --git a/src/collection.js b/src/collection.js index 83424023..1ad726de 100644 --- a/src/collection.js +++ b/src/collection.js @@ -34,7 +34,7 @@ const addOptions = { add: true, remove: false }; * belonging to this particular author, and so on. Collections maintain * indexes of their models, both in order, and for lookup by `id`. */ -class Collection extends EventEmitter { +class Collection extends EventEmitter(Object) { /** * Create a new **Collection**, perhaps to contain a specific type of `model`. * If a `comparator` is specified, the Collection will maintain diff --git a/src/element.js b/src/element.js index f06a8762..b89d8d2c 100644 --- a/src/element.js +++ b/src/element.js @@ -5,7 +5,7 @@ import EventEmitter from './eventemitter.js'; // Cached regex to split keys for `delegate`. const delegateEventSplitter = /^(\S+)\s*(.*)$/; -class ElementView extends HTMLElement { +class ElementView extends EventEmitter(HTMLElement) { /** * @typedef {import('./model.js').Model} Model @@ -33,8 +33,6 @@ class ElementView extends HTMLElement { constructor(options={}) { super(); - this.emitter = new EventEmitter(); - // Will be assigned to from Events this.stopListening = null; @@ -228,26 +226,6 @@ class ElementView extends HTMLElement { } return this; } - - /** - * @param {any} obj - * @param {string} name - * @param {EventCallback} [callback] - * @return {EventEmitter} - */ - listenTo(obj, name, callback) { - return this.emitter.listenTo.call(this, obj, name, callback); - } - - /** - * @param {any} [obj] - * @param {string} [name] - * @param {EventCallback} [callback] - * @return {EventEmitter} - */ - stopListening(obj, name, callback) { - return this.emitter.stopListening.call(this, obj, name, callback); - } } export default ElementView; diff --git a/src/eventemitter.js b/src/eventemitter.js index aff1fd60..84563775 100644 --- a/src/eventemitter.js +++ b/src/eventemitter.js @@ -11,170 +11,172 @@ import { eventsApi, onApi, offApi, onceMap, tryCatchOn, triggerApi } from './uti // A private global variable to share between listeners and listenees. let _listening; -class EventEmitter { - /** - * @typedef {import('./model.js').Model} Model - * @typedef {import('./collection.js').Collection} Collection - * @typedef {Record.} Options - * - * @callback EventCallback - * @param {any} event - * @param {Model} model - * @param {Collection} collection - * @param {Options} [options] - */ - - /** - * Bind an event to a `callback` function. Passing `"all"` will bind - * the callback to all events fired. - * @param {string} name - * @param {EventCallback} callback - * @param {any} context - * @return {EventEmitter} - */ - on(name, callback, context) { - this._events = eventsApi(onApi, this._events || {}, name, callback, { - context: context, - ctx: this, - listening: _listening, - }); - - if (_listening) { - const listeners = this._listeners || (this._listeners = {}); - listeners[_listening.id] = _listening; - // Allow the listening to use a counter, instead of tracking - // callbacks for library interop - _listening.interop = false; +export function EventEmitter(Base) { + return class EventEmitter extends Base { + /** + * @typedef {import('./model.js').Model} Model + * @typedef {import('./collection.js').Collection} Collection + * @typedef {Record.} Options + * + * @callback EventCallback + * @param {any} event + * @param {Model} model + * @param {Collection} collection + * @param {Options} [options] + */ + + /** + * Bind an event to a `callback` function. Passing `"all"` will bind + * the callback to all events fired. + * @param {string} name + * @param {EventCallback} callback + * @param {any} context + * @return {EventEmitter} + */ + on(name, callback, context) { + this._events = eventsApi(onApi, this._events || {}, name, callback, { + context: context, + ctx: this, + listening: _listening, + }); + + if (_listening) { + const listeners = this._listeners || (this._listeners = {}); + listeners[_listening.id] = _listening; + // Allow the listening to use a counter, instead of tracking + // callbacks for library interop + _listening.interop = false; + } + + return this; } - return this; - } - - /** - * Inversion-of-control versions of `on`. Tell *this* object to listen to - * an event in another object... keeping track of what it's listening to - * for easier unbinding later. - * @param {any} obj - * @param {string} name - * @param {EventCallback} [callback] - * @return {EventEmitter} - */ - listenTo(obj, name, callback) { - if (!obj) return this; - const id = obj._listenId || (obj._listenId = uniqueId('l')); - const listeningTo = this._listeningTo || (this._listeningTo = {}); - let listening = (_listening = listeningTo[id]); - - // This object is not listening to any other events on `obj` yet. - // Setup the necessary references to track the listening callbacks. - if (!listening) { - this._listenId || (this._listenId = uniqueId('l')); - listening = _listening = listeningTo[id] = new Listening(this, obj); + /** + * Inversion-of-control versions of `on`. Tell *this* object to listen to + * an event in another object... keeping track of what it's listening to + * for easier unbinding later. + * @param {any} obj + * @param {string} name + * @param {EventCallback} [callback] + * @return {EventEmitter} + */ + listenTo(obj, name, callback) { + if (!obj) return this; + const id = obj._listenId || (obj._listenId = uniqueId('l')); + const listeningTo = this._listeningTo || (this._listeningTo = {}); + let listening = (_listening = listeningTo[id]); + + // This object is not listening to any other events on `obj` yet. + // Setup the necessary references to track the listening callbacks. + if (!listening) { + this._listenId || (this._listenId = uniqueId('l')); + listening = _listening = listeningTo[id] = new Listening(this, obj); + } + + // Bind callbacks on obj. + const error = tryCatchOn(obj, name, callback, this); + _listening = undefined; + + if (error) throw error; + // If the target obj is not Backbone.Events, track events manually. + if (listening.interop) listening.start(name, callback, this, _listening); + + return this; } - // Bind callbacks on obj. - const error = tryCatchOn(obj, name, callback, this); - _listening = undefined; - - if (error) throw error; - // If the target obj is not Backbone.Events, track events manually. - if (listening.interop) listening.start(name, callback, this); - - return this; - } - - /** - * Remove one or many callbacks. If `context` is null, removes all - * callbacks with that function. If `callback` is null, removes all - * callbacks for the event. If `name` is null, removes all bound - * callbacks for all events. - * @param {string} name - * @param {EventCallback} callback - * @param {any} [context] - * @return {EventEmitter} - */ - off(name, callback, context) { - if (!this._events) return this; - this._events = eventsApi(offApi, this._events, name, callback, { - context: context, - listeners: this._listeners, - }); - - return this; - } - - /** - * Tell this object to stop listening to either specific events ... or - * to every object it's currently listening to. - * @param {any} [obj] - * @param {string} [name] - * @param {EventCallback} [callback] - * @return {EventEmitter} - */ - stopListening(obj, name, callback) { - const listeningTo = this._listeningTo; - if (!listeningTo) return this; - - const ids = obj ? [obj._listenId] : keys(listeningTo); - for (let i = 0; i < ids.length; i++) { - const listening = listeningTo[ids[i]]; - - // If listening doesn't exist, this object is not currently - // listening to obj. Break out early. - if (!listening) break; - - listening.obj.off(name, callback, this); - if (listening.interop) listening.stop(name, callback); + /** + * Remove one or many callbacks. If `context` is null, removes all + * callbacks with that function. If `callback` is null, removes all + * callbacks for the event. If `name` is null, removes all bound + * callbacks for all events. + * @param {string} name + * @param {EventCallback} callback + * @param {any} [context] + * @return {EventEmitter} + */ + off(name, callback, context) { + if (!this._events) return this; + this._events = eventsApi(offApi, this._events, name, callback, { + context: context, + listeners: this._listeners, + }); + + return this; } - if (isEmpty(listeningTo)) this._listeningTo = undefined; - - return this; - } - - /** - * Bind an event to only be triggered a single time. After the first time - * the callback is invoked, its listener will be removed. If multiple events - * are passed in using the space-separated syntax, the handler will fire - * once for each event, not once for a combination of all events. - * @param {string} name - * @param {EventCallback} callback - * @param {any} context - * @return {EventEmitter} - */ - once(name, callback, context) { - // Map the event into a `{event: once}` object. - const events = eventsApi(onceMap, {}, name, callback, this.off.bind(this)); - if (typeof name === 'string' && (context === null || context === undefined)) callback = undefined; - return this.on(events, callback, context); - } - - /** - * Inversion-of-control versions of `once`. - * @param {any} obj - * @param {string} name - * @param {EventCallback} [callback] - * @return {EventEmitter} - */ - listenToOnce(obj, name, callback) { - // Map the event into a `{event: once}` object. - const events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj)); - return this.listenTo(obj, events); - } - - /** - * Trigger one or many events, firing all bound callbacks. Callbacks are - * passed the same arguments as `trigger` is, apart from the event name - * (unless you're listening on `"all"`, which will cause your callback to - * receive the true name of the event as the first argument). - * @param {string} name - * @return {EventEmitter} - */ - trigger(name, ...args) { - if (!this._events) return this; - - eventsApi(triggerApi, this._events, name, undefined, args); - return this; - } + + /** + * Tell this object to stop listening to either specific events ... or + * to every object it's currently listening to. + * @param {any} [obj] + * @param {string} [name] + * @param {EventCallback} [callback] + * @return {EventEmitter} + */ + stopListening(obj, name, callback) { + const listeningTo = this._listeningTo; + if (!listeningTo) return this; + + const ids = obj ? [obj._listenId] : keys(listeningTo); + for (let i = 0; i < ids.length; i++) { + const listening = listeningTo[ids[i]]; + + // If listening doesn't exist, this object is not currently + // listening to obj. Break out early. + if (!listening) break; + + listening.obj.off(name, callback, this); + if (listening.interop) listening.stop(name, callback); + } + if (isEmpty(listeningTo)) this._listeningTo = undefined; + + return this; + } + + /** + * Bind an event to only be triggered a single time. After the first time + * the callback is invoked, its listener will be removed. If multiple events + * are passed in using the space-separated syntax, the handler will fire + * once for each event, not once for a combination of all events. + * @param {string} name + * @param {EventCallback} callback + * @param {any} context + * @return {EventEmitter} + */ + once(name, callback, context) { + // Map the event into a `{event: once}` object. + const events = eventsApi(onceMap, {}, name, callback, this.off.bind(this)); + if (typeof name === 'string' && (context === null || context === undefined)) callback = undefined; + return this.on(events, callback, context); + } + + /** + * Inversion-of-control versions of `once`. + * @param {any} obj + * @param {string} name + * @param {EventCallback} [callback] + * @return {EventEmitter} + */ + listenToOnce(obj, name, callback) { + // Map the event into a `{event: once}` object. + const events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj)); + return this.listenTo(obj, events); + } + + /** + * Trigger one or many events, firing all bound callbacks. Callbacks are + * passed the same arguments as `trigger` is, apart from the event name + * (unless you're listening on `"all"`, which will cause your callback to + * receive the true name of the event as the first argument). + * @param {string} name + * @return {EventEmitter} + */ + trigger(name, ...args) { + if (!this._events) return this; + + eventsApi(triggerApi, this._events, name, undefined, args); + return this; + } + }; } export default EventEmitter; diff --git a/src/events.js b/src/events.js deleted file mode 100644 index 41e11346..00000000 --- a/src/events.js +++ /dev/null @@ -1,328 +0,0 @@ -// Backbone.js 1.4.0 -// (c) 2010-2019 Jeremy Ashkenas and DocumentCloud -// Backbone may be freely distributed under the MIT license. - -// Events -// ------ - -// A module that can be mixed in to *any object* in order to provide it with -// a custom event channel. You may bind a callback to an event with `on` or -// remove with `off`; `trigger`-ing an event fires all callbacks in -// succession. -// -// let object = {}; -// extend(object, Backbone.Events); -// object.on('expand', function(){ alert('expanded'); }); -// object.trigger('expand'); -// - -import isEmpty from 'lodash-es/isEmpty.js'; -import keys from 'lodash-es/keys.js'; -import once from 'lodash-es/once.js'; -import uniqueId from 'lodash-es/uniqueId.js'; - -export const Events = {}; - -// Regular expression used to split event strings. -const eventSplitter = /\s+/; - -// A private global variable to share between listeners and listenees. -let _listening; - -// Iterates over the standard `event, callback` (as well as the fancy multiple -// space-separated events `"change blur", callback` and jQuery-style event -// maps `{event: callback}`). -const eventsApi = function(iteratee, events, name, callback, opts) { - let i = 0, names; - if (name && typeof name === 'object') { - // Handle event maps. - if (callback !== undefined && 'context' in opts && opts.context === undefined) opts.context = callback; - for (names = keys(name); i < names.length ; i++) { - events = eventsApi(iteratee, events, names[i], name[names[i]], opts); - } - } else if (name && eventSplitter.test(name)) { - // Handle space-separated event names by delegating them individually. - for (names = name.split(eventSplitter); i < names.length; i++) { - events = iteratee(events, names[i], callback, opts); - } - } else { - // Finally, standard events. - events = iteratee(events, name, callback, opts); - } - return events; -}; - -// Bind an event to a `callback` function. Passing `"all"` will bind -// the callback to all events fired. -Events.on = function(name, callback, context) { - this._events = eventsApi(onApi, this._events || {}, name, callback, { - context: context, - ctx: this, - listening: _listening - }); - - if (_listening) { - const listeners = this._listeners || (this._listeners = {}); - listeners[_listening.id] = _listening; - // Allow the listening to use a counter, instead of tracking - // callbacks for library interop - _listening.interop = false; - } - - return this; -}; - -// Inversion-of-control versions of `on`. Tell *this* object to listen to -// an event in another object... keeping track of what it's listening to -// for easier unbinding later. -Events.listenTo = function(obj, name, callback) { - if (!obj) return this; - const id = obj._listenId || (obj._listenId = uniqueId('l')); - const listeningTo = this._listeningTo || (this._listeningTo = {}); - let listening = _listening = listeningTo[id]; - - // This object is not listening to any other events on `obj` yet. - // Setup the necessary references to track the listening callbacks. - if (!listening) { - this._listenId || (this._listenId = uniqueId('l')); - listening = _listening = listeningTo[id] = new Listening(this, obj); - } - - // Bind callbacks on obj. - const error = tryCatchOn(obj, name, callback, this); - _listening = undefined; - - if (error) throw error; - // If the target obj is not Backbone.Events, track events manually. - if (listening.interop) listening.on(name, callback); - - return this; -}; - -// The reducing API that adds a callback to the `events` object. -const onApi = function(events, name, callback, options) { - if (callback) { - const handlers = events[name] || (events[name] = []); - const context = options.context, ctx = options.ctx, listening = options.listening; - if (listening) listening.count++; - - handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening}); - } - return events; -}; - -// An try-catch guarded #on function, to prevent poisoning the global -// `_listening` variable. -const tryCatchOn = function(obj, name, callback, context) { - try { - obj.on(name, callback, context); - } catch (e) { - return e; - } -}; - -// Remove one or many callbacks. If `context` is null, removes all -// callbacks with that function. If `callback` is null, removes all -// callbacks for the event. If `name` is null, removes all bound -// callbacks for all events. -Events.off = function(name, callback, context) { - if (!this._events) return this; - this._events = eventsApi(offApi, this._events, name, callback, { - context: context, - listeners: this._listeners - }); - - return this; -}; - -// Tell this object to stop listening to either specific events ... or -// to every object it's currently listening to. -Events.stopListening = function(obj, name, callback) { - const listeningTo = this._listeningTo; - if (!listeningTo) return this; - - const ids = obj ? [obj._listenId] : keys(listeningTo); - for (let i = 0; i < ids.length; i++) { - const listening = listeningTo[ids[i]]; - - // If listening doesn't exist, this object is not currently - // listening to obj. Break out early. - if (!listening) break; - - listening.obj.off(name, callback, this); - if (listening.interop) listening.off(name, callback); - } - if (isEmpty(listeningTo)) this._listeningTo = undefined; - - return this; -}; - -// The reducing API that removes a callback from the `events` object. -const offApi = function(events, name, callback, options) { - if (!events) return; - - const context = options.context, listeners = options.listeners; - let i = 0, names; - - // Delete all event listeners and "drop" events. - if (!name && !context && !callback) { - for (names = keys(listeners); i < names.length; i++) { - listeners[names[i]].cleanup(); - } - return; - } - - names = name ? [name] : keys(events); - for (; i < names.length; i++) { - name = names[i]; - const handlers = events[name]; - - // Bail out if there are no events stored. - if (!handlers) { - break; - } - - // Find any remaining events. - const remaining = []; - for (let j = 0; j < handlers.length; j++) { - const handler = handlers[j]; - if ( - callback && callback !== handler.callback && - callback !== handler.callback._callback || - context && context !== handler.context - ) { - remaining.push(handler); - } else { - const listening = handler.listening; - if (listening) listening.off(name, callback); - } - } - - // Replace events if there are any remaining. Otherwise, clean up. - if (remaining.length) { - events[name] = remaining; - } else { - delete events[name]; - } - } - - return events; -}; - -// Bind an event to only be triggered a single time. After the first time -// the callback is invoked, its listener will be removed. If multiple events -// are passed in using the space-separated syntax, the handler will fire -// once for each event, not once for a combination of all events. -Events.once = function(name, callback, context) { - // Map the event into a `{event: once}` object. - const events = eventsApi(onceMap, {}, name, callback, this.off.bind(this)); - if (typeof name === 'string' && (context === null || context === undefined)) callback = undefined; - return this.on(events, callback, context); -}; - -// Inversion-of-control versions of `once`. -Events.listenToOnce = function(obj, name, callback) { - // Map the event into a `{event: once}` object. - const events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj)); - return this.listenTo(obj, events); -}; - -// Reduces the event callbacks into a map of `{event: onceWrapper}`. -// `offer` unbinds the `onceWrapper` after it has been called. -const onceMap = function(map, name, callback, offer) { - if (callback) { - const _once = map[name] = once(function() { - offer(name, _once); - callback.apply(this, arguments); - }); - _once._callback = callback; - } - return map; -}; - -// Trigger one or many events, firing all bound callbacks. Callbacks are -// passed the same arguments as `trigger` is, apart from the event name -// (unless you're listening on `"all"`, which will cause your callback to -// receive the true name of the event as the first argument). -Events.trigger = function(name) { - if (!this._events) return this; - - const length = Math.max(0, arguments.length - 1); - const args = Array(length); - for (let i = 0; i < length; i++) args[i] = arguments[i + 1]; - - eventsApi(triggerApi, this._events, name, undefined, args); - return this; -}; - -// Handles triggering the appropriate event callbacks. -const triggerApi = function(objEvents, name, callback, args) { - if (objEvents) { - const events = objEvents[name]; - let allEvents = objEvents.all; - if (events && allEvents) allEvents = allEvents.slice(); - if (events) triggerEvents(events, args); - if (allEvents) triggerEvents(allEvents, [name].concat(args)); - } - return objEvents; -}; - -// A difficult-to-believe, but optimized internal dispatch function for -// triggering events. Tries to keep the usual cases speedy (most internal -// Backbone events have 3 arguments). -const triggerEvents = function(events, args) { - let ev, i = -1; - const l = events.length, - a1 = args[0], - a2 = args[1], - a3 = args[2]; - switch (args.length) { - case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; - case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; - case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; - case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; - default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; - } -}; - -// A listening class that tracks and cleans up memory bindings -// when all callbacks have been offed. -const Listening = function(listener, obj) { - this.id = listener._listenId; - this.listener = listener; - this.obj = obj; - this.interop = true; - this.count = 0; - this._events = undefined; -}; - -Listening.prototype.on = Events.on; - -// Offs a callback (or several). -// Uses an optimized counter if the listenee uses Backbone.Events. -// Otherwise, falls back to manual tracking to support events -// library interop. -Listening.prototype.off = function(name, callback) { - let cleanup; - if (this.interop) { - this._events = eventsApi(offApi, this._events, name, callback, { - context: undefined, - listeners: undefined - }); - cleanup = !this._events; - } else { - this.count--; - cleanup = this.count === 0; - } - if (cleanup) this.cleanup(); -}; - -// Cleans up memory bindings between the listener and the listenee. -Listening.prototype.cleanup = function() { - delete this.listener._listeningTo[this.obj._listenId]; - if (!this.interop) delete this.obj._listeners[this.id]; -}; - -// Aliases for backwards compatibility. -Events.bind = Events.on; -Events.unbind = Events.off; diff --git a/src/index.js b/src/index.js index c9b6b687..b11bc975 100644 --- a/src/index.js +++ b/src/index.js @@ -1,20 +1,18 @@ /* global global */ import ElementView from './element.js'; +import EventEmitter from './eventemitter.js'; import { Collection } from './collection.js'; -import { Events } from './events.js'; import { Model } from './model.js'; import { sync } from './helpers.js'; const skeletor = { Collection, ElementView, - Events, + EventEmitter, Model, sync, }; -Object.assign(skeletor, Events); - // Establish the root object, `window` (`self`) in the browser, or `global` on the server. // We use `self` instead of `window` for `WebWorker` support. const root = diff --git a/src/listening.js b/src/listening.js index 73e3088f..4bd49051 100644 --- a/src/listening.js +++ b/src/listening.js @@ -9,7 +9,7 @@ class Listening { /** @typedef {import('./eventemitter.js').default} EventEmitter */ /** - * @param {EventEmitter} listener + * @param {any} listener * @param {any} obj */ constructor(listener, obj) { @@ -25,20 +25,23 @@ class Listening { * @param {string} name * @param {Function} callback * @param {any} context + * @param {Listening} _listening */ - start(name, callback, context) { + start(name, callback, context, _listening) { this._events = eventsApi(onApi, this._events || {}, name, callback, { context: this.obj, ctx: context, - listening: this, + listening: _listening, }); - const listeners = this.obj._listeners || (this.obj._listeners = {}); - listeners[this.id] = this; + if (_listening) { + const listeners = this.obj._listeners || (this.obj._listeners = {}); + listeners[this.id] = this; - // Allow the listening to use a counter, instead of tracking - // callbacks for library interop - this.interop = false; + // Allow the listening to use a counter, instead of tracking + // callbacks for library interop + this.interop = false; + } return this; } diff --git a/src/model.js b/src/model.js index acea6ac0..f49ae533 100644 --- a/src/model.js +++ b/src/model.js @@ -33,7 +33,7 @@ import EventEmitter from './eventemitter.js'; * A discrete chunk of data and a bunch of useful, related methods for * performing computations and transformations on that data. */ -class Model extends EventEmitter { +class Model extends EventEmitter(Object) { /** * Create a new model with the specified attributes. A client id (`cid`) * is automatically generated and assigned for you. diff --git a/src/utils/events.js b/src/utils/events.js index 35dcc6c0..bc87d7cd 100644 --- a/src/utils/events.js +++ b/src/utils/events.js @@ -39,7 +39,7 @@ export function onApi(events, name, callback, options) { listening = options.listening; if (listening) listening.count++; - handlers.push({ callback: callback, context: context, ctx: context || ctx, listening: listening }); + handlers.push({ callback, context, ctx: context || ctx, listening }); } return events; } diff --git a/test/events.js b/test/events.js index 1e397496..432cec77 100644 --- a/test/events.js +++ b/test/events.js @@ -1,155 +1,208 @@ -(function(QUnit) { - - QUnit.module('Skeletor.Events'); - - QUnit.test('on and trigger', function(assert) { +(function (QUnit) { + class Foo extends Skeletor.EventEmitter(Object) { + constructor() { + super(); + this.counter = 0; + } + } + + class FooWith2Counters extends Skeletor.EventEmitter(Object) { + constructor() { + super(); + this.counterA = 0; + this.counterB = 0; + } + } + + QUnit.module('Skeletor.EventEmitter'); + + QUnit.test('on and trigger', function (assert) { assert.expect(2); - const obj = {counter: 0}; - _.extend(obj, Skeletor.Events); - obj.on('event', function() { obj.counter += 1; }); - obj.trigger('event'); - assert.equal(obj.counter, 1, 'counter should be incremented.'); - obj.trigger('event'); - obj.trigger('event'); - obj.trigger('event'); - obj.trigger('event'); - assert.equal(obj.counter, 5, 'counter should be incremented five times.'); + + const foo = new Foo(); + foo.on('event', function () { + foo.counter += 1; + }); + foo.trigger('event'); + assert.equal(foo.counter, 1, 'counter should be incremented.'); + foo.trigger('event'); + foo.trigger('event'); + foo.trigger('event'); + foo.trigger('event'); + assert.equal(foo.counter, 5, 'counter should be incremented five times.'); }); - QUnit.test('binding and triggering multiple events', function(assert) { + QUnit.test('binding and triggering multiple events', function (assert) { assert.expect(4); - const obj = {counter: 0}; - _.extend(obj, Skeletor.Events); - obj.on('a b c', function() { obj.counter += 1; }); + const foo = new Foo(); + foo.on('a b c', function () { + foo.counter += 1; + }); - obj.trigger('a'); - assert.equal(obj.counter, 1); + foo.trigger('a'); + assert.equal(foo.counter, 1); - obj.trigger('a b'); - assert.equal(obj.counter, 3); + foo.trigger('a b'); + assert.equal(foo.counter, 3); - obj.trigger('c'); - assert.equal(obj.counter, 4); + foo.trigger('c'); + assert.equal(foo.counter, 4); - obj.off('a c'); - obj.trigger('a b c'); - assert.equal(obj.counter, 5); + foo.off('a c'); + foo.trigger('a b c'); + assert.equal(foo.counter, 5); }); - QUnit.test('binding and triggering with event maps', function(assert) { - const obj = {counter: 0}; - _.extend(obj, Skeletor.Events); + QUnit.test('binding and triggering with event maps', function (assert) { + class Foo extends Skeletor.EventEmitter(Object) { + constructor() { + super(); + this.counter = 0; + } + + increment() { + this.counter += 1; + } + } - const increment = function() { - this.counter += 1; - }; + const foo = new Foo(); - obj.on({ - a: increment, - b: increment, - c: increment - }, obj); + foo.on( + { + a: foo.increment, + b: foo.increment, + c: foo.increment, + }, + foo, + ); - obj.trigger('a'); - assert.equal(obj.counter, 1); + foo.trigger('a'); + assert.equal(foo.counter, 1); - obj.trigger('a b'); - assert.equal(obj.counter, 3); + foo.trigger('a b'); + assert.equal(foo.counter, 3); - obj.trigger('c'); - assert.equal(obj.counter, 4); + foo.trigger('c'); + assert.equal(foo.counter, 4); - obj.off({ - a: increment, - c: increment - }, obj); - obj.trigger('a b c'); - assert.equal(obj.counter, 5); + foo.off( + { + a: foo.increment, + c: foo.increment, + }, + foo, + ); + foo.trigger('a b c'); + assert.equal(foo.counter, 5); }); - QUnit.test('binding and triggering multiple event names with event maps', function(assert) { - const obj = {counter: 0}; - _.extend(obj, Skeletor.Events); + QUnit.test('binding and triggering multiple event names with event maps', function (assert) { + class Foo extends Skeletor.EventEmitter(Object) { + constructor() { + super(); + this.counter = 0; + } + + increment() { + this.counter += 1; + } + } - const increment = function() { - this.counter += 1; - }; + const foo = new Foo(); - obj.on({ - 'a b c': increment + foo.on({ + 'a b c': foo.increment, }); - obj.trigger('a'); - assert.equal(obj.counter, 1); + foo.trigger('a'); + assert.equal(foo.counter, 1); - obj.trigger('a b'); - assert.equal(obj.counter, 3); + foo.trigger('a b'); + assert.equal(foo.counter, 3); - obj.trigger('c'); - assert.equal(obj.counter, 4); + foo.trigger('c'); + assert.equal(foo.counter, 4); - obj.off({ - 'a c': increment + foo.off({ + 'a c': foo.increment, }); - obj.trigger('a b c'); - assert.equal(obj.counter, 5); + foo.trigger('a b c'); + assert.equal(foo.counter, 5); }); - QUnit.test('binding and trigger with event maps context', function(assert) { + QUnit.test('binding and trigger with event maps context', function (assert) { assert.expect(2); - const obj = {counter: 0}; - const context = {}; - _.extend(obj, Skeletor.Events); - - obj.on({ - a: function() { - assert.strictEqual(this, context, 'defaults `context` to `callback` param'); - } - }, context).trigger('a'); - obj.off().on({ - a: function() { - assert.strictEqual(this, context, 'will not override explicit `context` param'); - } - }, this, context).trigger('a'); - }); - - QUnit.test('listenTo and stopListening', function(assert) { + const context = {}; + const foo = new Foo(); + + foo + .on( + { + a: function () { + assert.strictEqual(this, context, 'defaults `context` to `callback` param'); + }, + }, + context, + ) + .trigger('a'); + + foo + .off() + .on( + { + a: function () { + assert.strictEqual(this, context, 'will not override explicit `context` param'); + }, + }, + this, + context, + ) + .trigger('a'); + }); + + QUnit.test('listenTo and stopListening', function (assert) { assert.expect(1); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); - a.listenTo(b, 'all', function(){ assert.ok(true); }); + const a = new Foo(); + const b = new Foo(); + a.listenTo(b, 'all', function () { + assert.ok(true); + }); b.trigger('anything'); - a.listenTo(b, 'all', function(){ assert.ok(false); }); + a.listenTo(b, 'all', function () { + assert.ok(false); + }); a.stopListening(); b.trigger('anything'); }); - QUnit.test('listenTo and stopListening with event maps', function(assert) { + QUnit.test('listenTo and stopListening with event maps', function (assert) { assert.expect(4); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); - const cb = function(){ assert.ok(true); }; - a.listenTo(b, {event: cb}); + const a = new Foo(); + const b = new Foo(); + const cb = function () { + assert.ok(true); + }; + a.listenTo(b, { event: cb }); b.trigger('event'); - a.listenTo(b, {event2: cb}); + a.listenTo(b, { event2: cb }); b.on('event2', cb); - a.stopListening(b, {event2: cb}); + a.stopListening(b, { event2: cb }); b.trigger('event event2'); a.stopListening(); b.trigger('event event2'); }); - QUnit.test('stopListening with omitted args', function(assert) { + QUnit.test('stopListening with omitted args', function (assert) { assert.expect(2); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); + const a = new Foo(); + const b = new Foo(); const cb = () => assert.ok(true); a.listenTo(b, 'event', cb); b.on('event', cb); a.listenTo(b, 'event2', cb); - a.stopListening(null, {event: cb}); + a.stopListening(null, { event: cb }); b.trigger('event event2'); b.off(); a.listenTo(b, 'event event2', cb); @@ -158,24 +211,28 @@ b.trigger('event2'); }); - QUnit.test('listenToOnce', function(assert) { + QUnit.test('listenToOnce', function (assert) { assert.expect(2); // Same as the previous test, but we use once rather than having to explicitly unbind - const obj = {counterA: 0, counterB: 0}; - _.extend(obj, Skeletor.Events); - const incrA = function(){ obj.counterA += 1; obj.trigger('event'); }; - const incrB = function(){ obj.counterB += 1; }; - obj.listenToOnce(obj, 'event', incrA); - obj.listenToOnce(obj, 'event', incrB); - obj.trigger('event'); - assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.'); - assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.'); + const foo = new FooWith2Counters(); + const incrA = function () { + foo.counterA += 1; + foo.trigger('event'); + }; + const incrB = function () { + foo.counterB += 1; + }; + foo.listenToOnce(foo, 'event', incrA); + foo.listenToOnce(foo, 'event', incrB); + foo.trigger('event'); + assert.equal(foo.counterA, 1, 'counterA should have only been incremented once.'); + assert.equal(foo.counterB, 1, 'counterB should have only been incremented once.'); }); - QUnit.test('listenToOnce and stopListening', function(assert) { + QUnit.test('listenToOnce and stopListening', function (assert) { assert.expect(1); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); + const a = new Foo(); + const b = new Foo(); a.listenToOnce(b, 'all', () => assert.ok(true)); b.trigger('anything'); b.trigger('anything'); @@ -184,10 +241,10 @@ b.trigger('anything'); }); - QUnit.test('listenTo, listenToOnce and stopListening', function(assert) { + QUnit.test('listenTo, listenToOnce and stopListening', function (assert) { assert.expect(1); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); + const a = new Foo(); + const b = new Foo(); a.listenToOnce(b, 'all', () => assert.ok(true)); b.trigger('anything'); b.trigger('anything'); @@ -196,38 +253,50 @@ b.trigger('anything'); }); - QUnit.test('listenTo and stopListening with event maps', function(assert) { + QUnit.test('listenTo and stopListening with event maps', function (assert) { assert.expect(1); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); - a.listenTo(b, {change: function(){ assert.ok(true); }}); + const a = new Foo(); + const b = new Foo(); + a.listenTo(b, { + change: function () { + assert.ok(true); + }, + }); b.trigger('change'); - a.listenTo(b, {change: function(){ assert.ok(false); }}); + a.listenTo(b, { + change: function () { + assert.ok(false); + }, + }); a.stopListening(); b.trigger('change'); }); - QUnit.test('listenTo yourself', function(assert) { + QUnit.test('listenTo yourself', function (assert) { assert.expect(1); - const e = _.extend({}, Skeletor.Events); - e.listenTo(e, 'foo', function(){ assert.ok(true); }); - e.trigger('foo'); + const e = new Foo(); + e.listenTo(e, 'e', function () { + assert.ok(true); + }); + e.trigger('e'); }); - QUnit.test('listenTo yourself cleans yourself up with stopListening', function(assert) { + QUnit.test('listenTo yourself cleans yourself up with stopListening', function (assert) { assert.expect(1); - const e = _.extend({}, Skeletor.Events); - e.listenTo(e, 'foo', function(){ assert.ok(true); }); + const e = new Foo(); + e.listenTo(e, 'foo', function () { + assert.ok(true); + }); e.trigger('foo'); e.stopListening(); e.trigger('foo'); }); - QUnit.test('stopListening cleans up references', function(assert) { + QUnit.test('stopListening cleans up references', function (assert) { assert.expect(12); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); - const fn = function() {}; + const a = new Foo(); + const b = new Foo(); + const fn = function () {}; b.on('event', fn); a.listenTo(b, 'event', fn).stopListening(); assert.equal(_.size(a._listeningTo), 0); @@ -247,11 +316,11 @@ assert.equal(_.size(b._listeners), 0); }); - QUnit.test('stopListening cleans up references from listenToOnce', function(assert) { + QUnit.test('stopListening cleans up references from listenToOnce', function (assert) { assert.expect(12); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); - const fn = function() {}; + const a = new Foo(); + const b = new Foo(); + const fn = function () {}; b.on('event', fn); a.listenToOnce(b, 'event', fn).stopListening(); assert.equal(_.size(a._listeningTo), 0); @@ -271,11 +340,11 @@ assert.equal(_.size(b._listeners), 0); }); - QUnit.test('listenTo and off cleaning up references', function(assert) { + QUnit.test('listenTo and off cleaning up references', function (assert) { assert.expect(8); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); - const fn = function() {}; + const a = new Foo(); + const b = new Foo(); + const fn = function () {}; a.listenTo(b, 'event', fn); b.off(); assert.equal(_.size(a._listeningTo), 0); @@ -294,79 +363,90 @@ assert.equal(_.size(b._listeners), 0); }); - QUnit.test('listenTo and stopListening cleaning up references', function(assert) { + QUnit.test('listenTo and stopListening cleaning up references', function (assert) { assert.expect(2); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); - a.listenTo(b, 'all', function(){ assert.ok(true); }); + const a = new Foo(); + const b = new Foo(); + a.listenTo(b, 'all', function () { + assert.ok(true); + }); b.trigger('anything'); - a.listenTo(b, 'other', function(){ assert.ok(false); }); + a.listenTo(b, 'other', function () { + assert.ok(false); + }); a.stopListening(b, 'other'); a.stopListening(b, 'all'); assert.equal(_.size(a._listeningTo), 0); }); - QUnit.test('listenToOnce without context cleans up references after the event has fired', function(assert) { + QUnit.test('listenToOnce without context cleans up references after the event has fired', function (assert) { assert.expect(2); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); - a.listenToOnce(b, 'all', function(){ assert.ok(true); }); + const a = new Foo(); + const b = new Foo(); + a.listenToOnce(b, 'all', function () { + assert.ok(true); + }); b.trigger('anything'); assert.equal(_.size(a._listeningTo), 0); }); - QUnit.test('listenToOnce with event maps cleans up references', function(assert) { + QUnit.test('listenToOnce with event maps cleans up references', function (assert) { assert.expect(2); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); + const a = new Foo(); + const b = new Foo(); a.listenToOnce(b, { one: () => assert.ok(true), - two: () => assert.ok(false) + two: () => assert.ok(false), }); b.trigger('one'); assert.equal(_.size(a._listeningTo), 1); }); - QUnit.test('listenToOnce with event maps binds the correct `this`', function(assert) { + QUnit.test('listenToOnce with event maps binds the correct `this`', function (assert) { assert.expect(1); - const a = _.extend({}, Skeletor.Events); - const b = _.extend({}, Skeletor.Events); + const a = new Foo(); + const b = new Foo(); a.listenToOnce(b, { - one: function() { assert.ok(this === a); }, - two: function() { assert.ok(false); } + one: function () { + assert.ok(this === a); + }, + two: function () { + assert.ok(false); + }, }); b.trigger('one'); }); - QUnit.test("listenTo with empty callback doesn't throw an error", function(assert) { + QUnit.test("listenTo with empty callback doesn't throw an error", function (assert) { assert.expect(1); - const e = _.extend({}, Skeletor.Events); + const e = new Foo(); e.listenTo(e, 'foo', null); e.trigger('foo'); assert.ok(true); }); - QUnit.test('trigger all for each event', function(assert) { + QUnit.test('trigger all for each event', function (assert) { assert.expect(3); let a, b; - const obj = {counter: 0}; - _.extend(obj, Skeletor.Events); - obj.on('all', function(event) { - obj.counter++; - if (event === 'a') a = true; - if (event === 'b') b = true; - }) - .trigger('a b'); + const obj = new Foo(); + obj + .on('all', function (event) { + obj.counter++; + if (event === 'a') a = true; + if (event === 'b') b = true; + }) + .trigger('a b'); assert.ok(a); assert.ok(b); assert.equal(obj.counter, 2); }); - QUnit.test('on, then unbind all functions', function(assert) { + QUnit.test('on, then unbind all functions', function (assert) { assert.expect(1); - const obj = {counter: 0}; - _.extend(obj, Skeletor.Events); - const callback = function() { obj.counter += 1; }; + const obj = new Foo(); + const callback = function () { + obj.counter += 1; + }; obj.on('event', callback); obj.trigger('event'); obj.off('event'); @@ -374,13 +454,16 @@ assert.equal(obj.counter, 1, 'counter should have only been incremented once.'); }); - QUnit.test('bind two callbacks, unbind only one', function(assert) { + QUnit.test('bind two callbacks, unbind only one', function (assert) { assert.expect(2); - const obj = {counterA: 0, counterB: 0}; - _.extend(obj, Skeletor.Events); - const callback = function() { obj.counterA += 1; }; + const obj = new FooWith2Counters(); + const callback = function () { + obj.counterA += 1; + }; obj.on('event', callback); - obj.on('event', function() { obj.counterB += 1; }); + obj.on('event', function () { + obj.counterB += 1; + }); obj.trigger('event'); obj.off('event', callback); obj.trigger('event'); @@ -388,11 +471,10 @@ assert.equal(obj.counterB, 2, 'counterB should have been incremented twice.'); }); - QUnit.test('unbind a callback in the midst of it firing', function(assert) { + QUnit.test('unbind a callback in the midst of it firing', function (assert) { assert.expect(1); - const obj = {counter: 0}; - _.extend(obj, Skeletor.Events); - const callback = function() { + const obj = new Foo(); + const callback = function () { obj.counter += 1; obj.off('event', callback); }; @@ -403,12 +485,17 @@ assert.equal(obj.counter, 1, 'the callback should have been unbound.'); }); - QUnit.test('two binds that unbind themeselves', function(assert) { + QUnit.test('two binds that unbind themeselves', function (assert) { assert.expect(2); - const obj = {counterA: 0, counterB: 0}; - _.extend(obj, Skeletor.Events); - const incrA = function(){ obj.counterA += 1; obj.off('event', incrA); }; - const incrB = function(){ obj.counterB += 1; obj.off('event', incrB); }; + const obj = new FooWith2Counters(); + const incrA = function () { + obj.counterA += 1; + obj.off('event', incrA); + }; + const incrB = function () { + obj.counterB += 1; + obj.off('event', incrB); + }; obj.on('event', incrA); obj.on('event', incrB); obj.trigger('event'); @@ -418,51 +505,73 @@ assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.'); }); - QUnit.test('bind a callback with a default context when none supplied', function(assert) { + QUnit.test('bind a callback with a default context when none supplied', function (assert) { assert.expect(1); - const obj = _.extend({ - assertTrue: function() { + + // eslint-disable-next-line prefer-const + let obj; + + class Foo extends Skeletor.EventEmitter(Object) { + assertTrue() { assert.equal(this, obj, '`this` was bound to the callback'); } - }, Skeletor.Events); + } + obj = new Foo(); obj.once('event', obj.assertTrue); obj.trigger('event'); }); - QUnit.test('bind a callback with a supplied context', function(assert) { + QUnit.test('bind a callback with a supplied context', function (assert) { assert.expect(1); - const TestClass = function() { + const TestClass = function () { return this; }; - TestClass.prototype.assertTrue = function() { + TestClass.prototype.assertTrue = function () { assert.ok(true, '`this` was bound to the callback'); }; - const obj = _.extend({}, Skeletor.Events); - obj.on('event', function() { this.assertTrue(); }, new TestClass()); + const obj = new Foo(); + obj.on( + 'event', + function () { + this.assertTrue(); + }, + new TestClass(), + ); obj.trigger('event'); }); - QUnit.test('nested trigger with unbind', function(assert) { + QUnit.test('nested trigger with unbind', function (assert) { assert.expect(1); - const obj = {counter: 0}; - _.extend(obj, Skeletor.Events); - const incr1 = function(){ obj.counter += 1; obj.off('event', incr1); obj.trigger('event'); }; - const incr2 = function(){ obj.counter += 1; }; + const obj = new Foo(); + const incr1 = function () { + obj.counter += 1; + obj.off('event', incr1); + obj.trigger('event'); + }; + const incr2 = function () { + obj.counter += 1; + }; obj.on('event', incr1); obj.on('event', incr2); obj.trigger('event'); assert.equal(obj.counter, 3, 'counter should have been incremented three times'); }); - QUnit.test('callback list is not altered during trigger', function(assert) { + QUnit.test('callback list is not altered during trigger', function (assert) { assert.expect(2); let counter = 0; - const obj = _.extend({}, Skeletor.Events); - const incr = function(){ counter++; }; - const incrOn = function(){ obj.on('event all', incr); }; - const incrOff = function(){ obj.off('event all', incr); }; + const obj = new Foo(); + const incr = function () { + counter++; + }; + const incrOn = function () { + obj.on('event all', incr); + }; + const incrOff = function () { + obj.off('event all', incr); + }; obj.on('event all', incrOn).trigger('event'); assert.equal(counter, 0, 'on does not alter callback list'); @@ -471,43 +580,44 @@ assert.equal(counter, 2, 'off does not alter callback list'); }); - QUnit.test("#1282 - 'all' callback list is retrieved after each event.", function(assert) { + QUnit.test("#1282 - 'all' callback list is retrieved after each event.", function (assert) { assert.expect(1); var counter = 0; - const obj = _.extend({}, Skeletor.Events); - const incr = function(){ counter++; }; - obj.on('x', function() { - obj.on('y', incr).on('all', incr); - }) - .trigger('x y'); + const obj = new Foo(); + const incr = function () { + counter++; + }; + obj + .on('x', function () { + obj.on('y', incr).on('all', incr); + }) + .trigger('x y'); assert.strictEqual(counter, 2); }); - QUnit.test('if no callback is provided, `on` is a noop', function(assert) { - assert.expect(0); - _.extend({}, Skeletor.Events).on('test').trigger('test'); - }); + QUnit.test( + 'if callback is truthy but not a function, `on` should throw an error just like jQuery', + function (assert) { + assert.expect(1); + const view = new Foo().on('test', 'noop'); + assert.raises(function () { + view.trigger('test'); + }); + }, + ); - QUnit.test('if callback is truthy but not a function, `on` should throw an error just like jQuery', function(assert) { - assert.expect(1); - const view = _.extend({}, Skeletor.Events).on('test', 'noop'); - assert.raises(function() { - view.trigger('test'); - }); - }); - - QUnit.test('remove all events for a specific context', function(assert) { + QUnit.test('remove all events for a specific context', function (assert) { assert.expect(4); - const obj = _.extend({}, Skeletor.Events); + const obj = new Foo(); obj.on('x y all', () => assert.ok(true)); obj.on('x y all', () => assert.ok(false), obj); obj.off(null, null, obj); obj.trigger('x y'); }); - QUnit.test('remove all events for a specific callback', function(assert) { + QUnit.test('remove all events for a specific callback', function (assert) { assert.expect(4); - const obj = _.extend({}, Skeletor.Events); + const obj = new Foo(); const success = () => assert.ok(true); const fail = () => assert.ok(false); obj.on('x y all', success); @@ -516,22 +626,26 @@ obj.trigger('x y'); }); - QUnit.test('#1310 - off does not skip consecutive events', function(assert) { + QUnit.test('#1310 - off does not skip consecutive events', function (assert) { assert.expect(0); - const obj = _.extend({}, Skeletor.Events); + const obj = new Foo(); obj.on('event', () => assert.ok(false), obj); obj.on('event', () => assert.ok(false), obj); obj.off(null, null, obj); obj.trigger('event'); }); - QUnit.test('once', function(assert) { + QUnit.test('once', function (assert) { assert.expect(2); // Same as the previous test, but we use once rather than having to explicitly unbind - const obj = {counterA: 0, counterB: 0}; - _.extend(obj, Skeletor.Events); - const incrA = function(){ obj.counterA += 1; obj.trigger('event'); }; - const incrB = function(){ obj.counterB += 1; }; + const obj = new FooWith2Counters(); + const incrA = function () { + obj.counterA += 1; + obj.trigger('event'); + }; + const incrB = function () { + obj.counterB += 1; + }; obj.once('event', incrA); obj.once('event', incrB); obj.trigger('event'); @@ -539,54 +653,55 @@ assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.'); }); - QUnit.test('once variant one', function(assert) { + QUnit.test('once variant one', function (assert) { assert.expect(3); - const f = function(){ assert.ok(true); }; - - const a = _.extend({}, Skeletor.Events).once('event', f); - const b = _.extend({}, Skeletor.Events).on('event', f); - + const f = function () { + assert.ok(true); + }; + const a = new Foo().once('event', f); + const b = new Foo().on('event', f); a.trigger('event'); - b.trigger('event'); b.trigger('event'); }); - QUnit.test('once variant two', function(assert) { + QUnit.test('once variant two', function (assert) { assert.expect(3); - const f = function(){ assert.ok(true); }; - const obj = _.extend({}, Skeletor.Events); + const f = function () { + assert.ok(true); + }; + const obj = new Foo(); - obj - .once('event', f) - .on('event', f) - .trigger('event') - .trigger('event'); + obj.once('event', f).on('event', f).trigger('event').trigger('event'); }); - QUnit.test('once with off', function(assert) { + QUnit.test('once with off', function (assert) { assert.expect(0); - const f = function(){ assert.ok(true); }; - const obj = _.extend({}, Skeletor.Events); + const f = function () { + assert.ok(true); + }; + const obj = new Foo(); obj.once('event', f); obj.off('event', f); obj.trigger('event'); }); - QUnit.test('once with event maps', function(assert) { - const obj = {counter: 0}; - _.extend(obj, Skeletor.Events); + QUnit.test('once with event maps', function (assert) { + const obj = new Foo(); - const increment = function() { + const increment = function () { this.counter += 1; }; - obj.once({ - a: increment, - b: increment, - c: increment - }, obj); + obj.once( + { + a: increment, + b: increment, + c: increment, + }, + obj, + ); obj.trigger('a'); assert.equal(obj.counter, 1); @@ -601,85 +716,84 @@ assert.equal(obj.counter, 3); }); - QUnit.test('bind a callback with a supplied context using once with object notation', function(assert) { + QUnit.test('bind a callback with a supplied context using once with object notation', function (assert) { assert.expect(1); - const obj = {counter: 0}; const context = {}; - _.extend(obj, Skeletor.Events); + const obj = new Foo(); - obj.once({ - a: function() { - assert.strictEqual(this, context, 'defaults `context` to `callback` param'); - } - }, context).trigger('a'); - }); - - QUnit.test('once with off only by context', function(assert) { + obj + .once( + { + a: function () { + assert.strictEqual(this, context, 'defaults `context` to `callback` param'); + }, + }, + context, + ) + .trigger('a'); + }); + + QUnit.test('once with off only by context', function (assert) { assert.expect(0); const context = {}; - const obj = _.extend({}, Skeletor.Events); - obj.once('event', function(){ assert.ok(false); }, context); + const obj = new Foo(); + obj.once( + 'event', + function () { + assert.ok(false); + }, + context, + ); obj.off(null, null, context); obj.trigger('event'); }); - QUnit.test('Skeletor object inherits Events', function(assert) { - assert.ok(Skeletor.on === Skeletor.Events.on); - }); - - QUnit.test('once with asynchronous events', function(assert) { + QUnit.test('once with asynchronous events', function (assert) { const done = assert.async(); assert.expect(1); - const func = _.debounce(() => { assert.ok(true); done() }, 50); - const obj = _.extend({}, Skeletor.Events).once('async', func); + const func = _.debounce(() => { + assert.ok(true); + done(); + }, 50); + const obj = new Foo().once('async', func); obj.trigger('async'); obj.trigger('async'); }); - QUnit.test('once with multiple events.', function(assert) { + QUnit.test('once with multiple events.', function (assert) { assert.expect(2); - const obj = _.extend({}, Skeletor.Events); + const obj = new Foo(); obj.once('x y', () => assert.ok(true)); obj.trigger('x y'); }); - QUnit.test('Off during iteration with once.', function(assert) { + QUnit.test('Off during iteration with once.', function (assert) { assert.expect(2); - const obj = _.extend({}, Skeletor.Events); - const f = function(){ this.off('event', f); }; + const obj = new Foo(); + const f = function () { + this.off('event', f); + }; obj.on('event', f); - obj.once('event', function(){}); - obj.on('event', function(){ assert.ok(true); }); - - obj.trigger('event'); - obj.trigger('event'); - }); - - QUnit.test('`once` on `all` should work as expected', function(assert) { - assert.expect(1); - Skeletor.once('all', function() { + obj.once('event', function () {}); + obj.on('event', function () { assert.ok(true); - Skeletor.trigger('all'); }); - Skeletor.trigger('all'); - }); - QUnit.test('once without a callback is a noop', function(assert) { - assert.expect(0); - _.extend({}, Skeletor.Events).once('event').trigger('event'); + obj.trigger('event'); + obj.trigger('event'); }); - QUnit.test('listenToOnce without a callback is a noop', function(assert) { + QUnit.test('listenToOnce without a callback is a noop', function (assert) { assert.expect(0); - const obj = _.extend({}, Skeletor.Events); + const obj = new Foo(); obj.listenToOnce(obj, 'event').trigger('event'); }); - QUnit.test('event functions are chainable', function(assert) { - const obj = _.extend({}, Skeletor.Events); - const obj2 = _.extend({}, Skeletor.Events); - const fn = function() {}; + QUnit.test('event functions are chainable', function (assert) { + const obj = new Foo(); + const obj2 = new Foo(); + const fn = function () {}; assert.equal(obj, obj.trigger('noeventssetyet')); assert.equal(obj, obj.off('noeventssetyet')); assert.equal(obj, obj.stopListening('noeventssetyet')); @@ -693,48 +807,50 @@ assert.equal(obj, obj.stopListening()); }); - QUnit.test('#3448 - listenToOnce with space-separated events', function(assert) { + QUnit.test('#3448 - listenToOnce with space-separated events', function (assert) { assert.expect(2); - const one = _.extend({}, Skeletor.Events); - const two = _.extend({}, Skeletor.Events); + const one = new Foo(); + const two = new Foo(); let count = 1; - one.listenToOnce(two, 'x y', function(n) { assert.ok(n === count++); }); + one.listenToOnce(two, 'x y', function (n) { + assert.ok(n === count++); + }); two.trigger('x', 1); two.trigger('x', 1); two.trigger('y', 2); two.trigger('y', 2); }); - QUnit.test('#3611 - listenTo is compatible with non-Skeletor event libraries', function(assert) { - const obj = _.extend({}, Skeletor.Events); + QUnit.test('#3611 - listenTo is compatible with non-Skeletor event libraries', function (assert) { + const obj = new Foo(); const other = { events: {}, - on: function(name, callback) { + on: function (name, callback) { this.events[name] = callback; }, - trigger: function(name) { + trigger: function (name) { this.events[name](); - } + }, }; obj.listenTo(other, 'test', () => assert.ok(true)); other.trigger('test'); }); - QUnit.test('#3611 - stopListening is compatible with non-Skeletor event libraries', function(assert) { - const obj = _.extend({}, Skeletor.Events); + QUnit.test('#3611 - stopListening is compatible with non-Skeletor event libraries', function (assert) { + const obj = new Foo(); const other = { events: {}, - on: function(name, callback) { + on: function (name, callback) { this.events[name] = callback; }, - off: function() { + off: function () { this.events = {}; }, - trigger: function(name) { + trigger: function (name) { const fn = this.events[name]; if (fn) fn(); - } + }, }; obj.listenTo(other, 'test', () => assert.ok(false)); From 2849020ae76d272fda6f382743830f77c090b44e Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 26 Oct 2023 16:45:02 +0200 Subject: [PATCH 2/3] Export files from index.js --- src/index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/index.js b/src/index.js index b11bc975..8deac8ee 100644 --- a/src/index.js +++ b/src/index.js @@ -35,3 +35,11 @@ skeletor.noConflict = function () { root.Skeletor = skeletor; export default skeletor; + +export { + Collection, + ElementView, + EventEmitter, + Model, + sync, +} From dc5acf92369fffef548252d5ba2e9ba754fa262c Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 26 Oct 2023 17:12:31 +0200 Subject: [PATCH 3/3] Fix type errors, add type definitions and remove overview.js --- Makefile | 2 + package.json | 1 + src/collection.js | 103 +++--- src/eventemitter.js | 5 + src/model.js | 4 +- src/overview.js | 191 ----------- src/storage.js | 4 +- src/types/collection.d.ts | 372 ++++++++++++++++++++++ src/types/collection.d.ts.map | 1 + src/types/drivers/sessionStorage.d.ts | 26 ++ src/types/drivers/sessionStorage.d.ts.map | 1 + src/types/element.d.ts | 111 +++++++ src/types/element.d.ts.map | 1 + src/types/eventemitter.d.ts | 93 ++++++ src/types/eventemitter.d.ts.map | 1 + src/types/helpers.d.ts | 62 ++++ src/types/helpers.d.ts.map | 1 + src/types/index.d.ts | 18 ++ src/types/index.d.ts.map | 1 + src/types/listening.d.ts | 40 +++ src/types/listening.d.ts.map | 1 + src/types/model.d.ts | 233 ++++++++++++++ src/types/model.d.ts.map | 1 + src/types/storage.d.ts | 34 ++ src/types/storage.d.ts.map | 1 + src/types/utils/events.d.ts | 28 ++ src/types/utils/events.d.ts.map | 1 + test/collection.js | 7 +- tsconfig.json | 7 +- 29 files changed, 1095 insertions(+), 256 deletions(-) delete mode 100644 src/overview.js create mode 100644 src/types/collection.d.ts create mode 100644 src/types/collection.d.ts.map create mode 100644 src/types/drivers/sessionStorage.d.ts create mode 100644 src/types/drivers/sessionStorage.d.ts.map create mode 100644 src/types/element.d.ts create mode 100644 src/types/element.d.ts.map create mode 100644 src/types/eventemitter.d.ts create mode 100644 src/types/eventemitter.d.ts.map create mode 100644 src/types/helpers.d.ts create mode 100644 src/types/helpers.d.ts.map create mode 100644 src/types/index.d.ts create mode 100644 src/types/index.d.ts.map create mode 100644 src/types/listening.d.ts create mode 100644 src/types/listening.d.ts.map create mode 100644 src/types/model.d.ts create mode 100644 src/types/model.d.ts.map create mode 100644 src/types/storage.d.ts create mode 100644 src/types/storage.d.ts.map create mode 100644 src/types/utils/events.d.ts create mode 100644 src/types/utils/events.d.ts.map diff --git a/Makefile b/Makefile index c2d3d98a..b04391e5 100644 --- a/Makefile +++ b/Makefile @@ -14,11 +14,13 @@ node_modules: package.json package-lock.json npm install build: node_modules + npm run types npm run build dist: build check: node_modules build eslint + npm run types npm run test .PHONY: eslint diff --git a/package.json b/package.json index 412d1e72..bf0bba3d 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "description": "Modernized Backbone with web components", "url": "https://github.com/conversejs/skeletor", "main": "src/index.js", + "types": "src/types/index.d.ts", "browser": "dist/skeletor.js", "keywords": [ "model", diff --git a/src/collection.js b/src/collection.js index 1ad726de..14d2a420 100644 --- a/src/collection.js +++ b/src/collection.js @@ -51,6 +51,8 @@ class Collection extends EventEmitter(Object) { this._reset(); this.initialize.apply(this, arguments); if (models) this.reset(models, Object.assign({ silent: true }, options)); + + this[Symbol.iterator] = this.values; } /** @@ -280,10 +282,10 @@ class Collection extends EventEmitter(Object) { console.error(e); resolve(); }, - }) + }), ); }); - }) + }), ); await this.browserStorage.clear(); this.reset(); @@ -402,7 +404,7 @@ class Collection extends EventEmitter(Object) { sortBy(iteratee) { return sortBy( this.models, - isFunction(iteratee) ? iteratee : (m) => (isString(iteratee) ? m.get(iteratee) : m.matches(iteratee)) + isFunction(iteratee) ? iteratee : (m) => (isString(iteratee) ? m.get(iteratee) : m.matches(iteratee)), ); } @@ -452,7 +454,7 @@ class Collection extends EventEmitter(Object) { findLastIndex(pred, fromIndex) { return this.models.findLastIndex( isFunction(pred) ? pred : (m) => (isString(pred) ? m.get(pred) : m.matches(pred)), - fromIndex + fromIndex, ); } @@ -718,7 +720,7 @@ class Collection extends EventEmitter(Object) { _prepareModel(attrs, options) { if (this._isModel(attrs)) { if (!attrs.collection) attrs.collection = this; - return /** @type {Model} */(attrs); + return /** @type {Model} */ (attrs); } options = options ? clone(options) : {}; options.collection = this; @@ -822,26 +824,6 @@ class Collection extends EventEmitter(Object) { } } -// Defining an @@iterator method implements JavaScript's Iterable protocol. -// In modern ES2015 browsers, this value is found at Symbol.iterator. -const $$iterator = typeof Symbol === 'function' && Symbol.iterator; -if ($$iterator) { - Collection.prototype[$$iterator] = Collection.prototype.values; -} - -// CollectionIterator -// ------------------ - -// A CollectionIterator implements JavaScript's Iterator protocol, allowing the -// use of `for of` loops in modern browsers and interoperation between -// Collection and other JavaScript functions and third-party libraries -// which can operate on Iterables. -const CollectionIterator = function (collection, kind) { - this._collection = collection; - this._kind = kind; - this._index = 0; -}; - // This "enum" defines the three possible kinds of values which can be emitted // by a CollectionIterator that correspond to the values(), keys() and entries() // methods on Collection, respectively. @@ -849,42 +831,55 @@ const ITERATOR_VALUES = 1; const ITERATOR_KEYS = 2; const ITERATOR_KEYSVALUES = 3; -// All Iterators should themselves be Iterable. -if ($$iterator) { - CollectionIterator.prototype[$$iterator] = function () { - return this; - }; -} +class CollectionIterator { + /** + * A CollectionIterator implements JavaScript's Iterator protocol, allowing the + * use of `for of` loops in modern browsers and interoperation between + * Collection and other JavaScript functions and third-party libraries + * which can operate on Iterables. + * @param {Collection} collection + * @param {Number} kind + */ + constructor(collection, kind) { + this._collection = collection; + this._kind = kind; + this._index = 0; + } + + next() { + if (this._collection) { + // Only continue iterating if the iterated collection is long enough. + if (this._index < this._collection.length) { + const model = this._collection.at(this._index); + this._index++; -CollectionIterator.prototype.next = function () { - if (this._collection) { - // Only continue iterating if the iterated collection is long enough. - if (this._index < this._collection.length) { - const model = this._collection.at(this._index); - this._index++; - - // Construct a value depending on what kind of values should be iterated. - let value; - if (this._kind === ITERATOR_VALUES) { - value = model; - } else { - const id = this._collection.modelId(model.attributes); - if (this._kind === ITERATOR_KEYS) { - value = id; + // Construct a value depending on what kind of values should be iterated. + let value; + if (this._kind === ITERATOR_VALUES) { + value = model; } else { - // ITERATOR_KEYSVALUES - value = [id, model]; + const id = this._collection.modelId(model.attributes); + if (this._kind === ITERATOR_KEYS) { + value = id; + } else { + // ITERATOR_KEYSVALUES + value = [id, model]; + } } + return { value: value, done: false }; } - return { value: value, done: false }; + + // Once exhausted, remove the reference to the collection so future + // calls to the next method always return done. + this._collection = undefined; } - // Once exhausted, remove the reference to the collection so future - // calls to the next method always return done. - this._collection = undefined; + return { value: undefined, done: true }; } - return { value: undefined, done: true }; -}; + [Symbol.iterator]() { + return this; + } +} export { Collection }; diff --git a/src/eventemitter.js b/src/eventemitter.js index 84563775..7906bb84 100644 --- a/src/eventemitter.js +++ b/src/eventemitter.js @@ -11,6 +11,11 @@ import { eventsApi, onApi, offApi, onceMap, tryCatchOn, triggerApi } from './uti // A private global variable to share between listeners and listenees. let _listening; +/** + * @function + * @template {new(...args: any[]) => {}} ClassConstructor + * @param {ClassConstructor} Base + */ export function EventEmitter(Base) { return class EventEmitter extends Base { /** diff --git a/src/model.js b/src/model.js index f49ae533..1f8d5947 100644 --- a/src/model.js +++ b/src/model.js @@ -359,8 +359,8 @@ class Model extends EventEmitter(Object) { * Set a hash of model attributes, and sync the model to the server. * If the server returns an attributes hash that differs, the model's * state will be `set` again. - * @param {string} key - * @param {string|Options} val + * @param {string|Attributes} key + * @param {string|Options} [val] * @param {Options} [options] */ save(key, val, options) { diff --git a/src/overview.js b/src/overview.js deleted file mode 100644 index df61482a..00000000 --- a/src/overview.js +++ /dev/null @@ -1,191 +0,0 @@ -/*! - * Copyright (c) JC Brand - */ -import debounce from 'lodash-es/debounce.js'; -import difference from 'lodash-es/difference.js'; -import drop from 'lodash-es/drop.js'; -import every from 'lodash-es/every.js'; -import extend from 'lodash-es/extend.js'; -import filter from 'lodash-es/filter.js'; -import find from 'lodash-es/find.js'; -import first from 'lodash-es/first.js'; -import forEach from 'lodash-es/forEach.js'; -import get from 'lodash-es/get.js'; -import head from 'lodash-es/head.js'; -import includes from 'lodash-es/includes.js'; -import indexOf from 'lodash-es/indexOf.js'; -import initial from 'lodash-es/initial.js'; -import invoke from 'lodash-es/invoke.js'; -import isEmpty from 'lodash-es/isEmpty.js'; -import last from 'lodash-es/last.js'; -import lastIndexOf from 'lodash-es/lastIndexOf.js'; -import map from 'lodash-es/map.js'; -import max from 'lodash-es/max.js'; -import min from 'lodash-es/min.js'; -import reduce from 'lodash-es/reduce.js'; -import reduceRight from 'lodash-es/reduceRight.js'; -import reject from 'lodash-es/reject.js'; -import rest from 'lodash-es/rest.js'; -import sample from 'lodash-es/sample.js'; -import shuffle from 'lodash-es/shuffle.js'; -import size from 'lodash-es/size.js'; -import some from 'lodash-es/some.js'; -import sortBy from 'lodash-es/sortBy.js'; -import tail from 'lodash-es/tail.js'; -import take from 'lodash-es/take.js'; -import toArray from 'lodash-es/toArray.js'; -import without from 'lodash-es/without.js'; -import { View } from "./view"; - - -const Overview = function (options) { - /* An Overview is a View that contains and keeps track of sub-views. - * Kind of like what a Collection is to a Model. - */ - this.views = {}; - this.keys = () => Object.keys(this.views); - this.getAll = () => this.views; - this.get = id => this.views[id]; - - /* Exclusive get. Returns all instances except the given id. */ - this.xget = id => { - return this.keys() - .filter(k => (k !== id)) - .reduce((acc, k) => { - acc[k] = this.views[k] - return acc; - }, {}); - } - - this.add = (id, view) => { - this.views[id] = view; - return view; - }; - - this.remove = id => { - if (typeof id === "undefined") { - new View().remove.apply(this); - } - const view = this.views[id]; - if (view) { - delete this.views[id]; - view.remove(); - return view; - } - }; - - this.removeAll = () => { - this.keys().forEach(id => this.remove(id)); - return this; - } - - View.apply(this, Array.prototype.slice.apply(arguments)); -}; - - -const methods = { - includes, difference, drop, - every, filter, find, - first, forEach, head, - indexOf, initial, invoke, isEmpty, - last, lastIndexOf, map, max, min, reduce, - reduceRight, reject, rest, sample, - shuffle, size, some, sortBy, tail, take, - toArray, without -} -Object.keys(methods).forEach(name => { - Overview.prototype[name] = function() { - const args = Array.prototype.slice.call(arguments); - args.unshift(this.views); - return methods[name].apply(this, args); - }; -}); - -Object.assign(Overview.prototype, View.prototype); -Overview.extend = View.extend; - - -const OrderedListView = Overview.extend({ - /* An OrderedListView is a special type of Overview which adds some - * methods and conventions for rendering an ordered list of elements. - */ - // The `listItems` attribute denotes the path (from this View) to the - // list of items. - listItems: 'model', - // The `sortEvent` attribute specifies the event which should cause the - // ordered list to be sorted. - sortEvent: 'change', - // If false, we debounce sorting and inserting the new item - // (for improved performance when a large amount of items get added all at once) - // Otherwise we immediately sort the items and insert the new item. - sortImmediatelyOnAdd: false, - // The `listSelector` is the selector used to query for the DOM list - // element which contains the ordered items. - listSelector: '.ordered-items', - // The `itemView` is constructor which should be called to create a - // View for a new item. - ItemView: undefined, - // The `subviewIndex` is the attribute of the list element model which - // acts as the index of the subview in the overview. - // An overview is a "Collection" of views, and they can be retrieved - // via an index. By default this is the 'id' attribute, but it could be - // set to something else. - subviewIndex: 'id', - - initialize () { - this.sortEventually = debounce(() => this.sortAndPositionAllItems(), 100); - this.items = get(this, this.listItems); - this.items.on('remove', this.removeView, this); - this.items.on('reset', this.removeAll, this); - - this.items.on('add', (a, b) => { - if (this.sortImmediatelyOnAdd) { - this.sortAndPositionAllItems(); - } else { - this.sortEventually(); - } - }); - - if (this.sortEvent) { - this.items.on(this.sortEvent, this.sortEventually, this); - } - }, - - createItemView (item) { - let item_view = this.get(item.get(this.subviewIndex)); - if (!item_view) { - item_view = new this.ItemView({model: item}); - this.add(item.get(this.subviewIndex), item_view); - } else { - item_view.model = item; - item_view.initialize(); - } - item_view.render(); - return item_view; - }, - - removeView (item) { - this.remove(item.get(this.subviewIndex)); - }, - - sortAndPositionAllItems () { - if (!this.items.length) { - return; - } - this.items.sort(); - - const list_el = this.el.querySelector(this.listSelector); - const div = document.createElement('div'); - list_el.parentNode.replaceChild(div, list_el); - this.items.forEach(item => { - let view = this.get(item.get(this.subviewIndex)); - if (!view) { - view = this.createItemView(item) - } - list_el.insertAdjacentElement('beforeend', view.el); - }); - div.parentNode.replaceChild(list_el, div); - } -}); - -export { OrderedListView, Overview } diff --git a/src/storage.js b/src/storage.js index 26eb96e4..f64179de 100644 --- a/src/storage.js +++ b/src/storage.js @@ -8,7 +8,7 @@ import localForage from 'localforage/src/localforage'; import mergebounce from 'mergebounce'; import sessionStorageWrapper from './drivers/sessionStorage.js'; import { extendPrototype as extendPrototypeWithSetItems } from 'localforage-setitems'; -import { extendPrototype as extendPrototypeWithGetItems } from '@converse/localforage-getitems'; +import { extendPrototype as extendPrototypeWithGetItems } from '@converse/localforage-getitems/dist/localforage-getitems.es6'; import { guid } from './helpers.js'; const IN_MEMORY = memoryDriver._driver; @@ -111,7 +111,7 @@ class Storage { // the attributes dance again. model.attributes = new_attributes; } - promise = that.update(model, options); + promise = that.update(model); if (options.wait) { model.attributes = original_attributes; } diff --git a/src/types/collection.d.ts b/src/types/collection.d.ts new file mode 100644 index 00000000..f6112ab2 --- /dev/null +++ b/src/types/collection.d.ts @@ -0,0 +1,372 @@ +export type Options = Record; +export type Attributes = Record; +export type Storage = import('./storage.js').default; +export type CollectionOptions = Record; +declare const Collection_base: { + new (...args: any[]): { + on(name: string, callback: (event: any, model: Model, collection: Collection, options: Record) => any, context: any): any; + _events: any; /** + * Create a new **Collection**, perhaps to contain a specific type of `model`. + * If a `comparator` is specified, the Collection will maintain + * its models in sort order, as they're added and removed. + * @param {Model[]} models + * @param {CollectionOptions} options + */ + _listeners: {}; + listenTo(obj: any, name: string, callback?: (event: any, model: Model, collection: Collection, options: Record) => any): any; + _listeningTo: {}; + _listenId: any; + off(name: string, callback: (event: any, model: Model, collection: Collection, options: Record) => any, context?: any): any; + stopListening(obj?: any, name?: string, callback?: (event: any, model: Model, collection: Collection, options: Record) => any): any; + once(name: string, callback: (event: any, model: Model, collection: Collection, options: Record) => any, context: any): any; + listenToOnce(obj: any, name: string, callback?: (event: any, model: Model, collection: Collection, options: Record) => any): any; + trigger(name: string, ...args: any[]): any; + }; +} & ObjectConstructor; +/** + * @typedef {Record.} Options + * @typedef {Record.} Attributes + * + * @typedef {import('./storage.js').default} Storage + * + * @typedef {Record.} CollectionOptions + * @property {Model} [model] + * @property {Function} [comparator] + */ +/** + * If models tend to represent a single row of data, a Collection is + * more analogous to a table full of data ... or a small slice or page of that + * table, or a collection of rows that belong together for a particular reason + * -- all of the messages in this particular folder, all of the documents + * belonging to this particular author, and so on. Collections maintain + * indexes of their models, both in order, and for lookup by `id`. + */ +export class Collection extends Collection_base { + /** + * Create a new **Collection**, perhaps to contain a specific type of `model`. + * If a `comparator` is specified, the Collection will maintain + * its models in sort order, as they're added and removed. + * @param {Model[]} models + * @param {CollectionOptions} options + */ + constructor(models: Model[], options: CollectionOptions, ...args: any[]); + _model: any; + comparator: any; + /** + * @param {Storage} storage + */ + set browserStorage(arg: import("./storage.js").default); + /** + * @returns {Storage} storage + */ + get browserStorage(): import("./storage.js").default; + _browserStorage: import("./storage.js").default; + /** + * @param {Model} model + */ + set model(arg: typeof Model); + /** + * The default model for a collection is just a **Model**. + * This should be overridden in most cases. + * @returns {typeof Model} + */ + get model(): typeof Model; + get length(): any; + /** + * preinitialize is an empty function by default. You can override it with a function + * or object. preinitialize will run before any instantiation logic is run in the Collection. + */ + preinitialize(): void; + /** + * Initialize is an empty function by default. Override it with your own + * initialization logic. + */ + initialize(): void; + /** + * The JSON representation of a Collection is an array of the + * models' attributes. + *@param {Options} options + */ + toJSON(options: Options): any; + /** + *@param {string} method + *@param {Model|Collection} model + *@param {Options} options + */ + sync(method: string, model: Model | Collection, options: Options): any; + /** + * Add a model, or list of models to the set. `models` may be + * Models or raw JavaScript objects to be converted to Models, or any + * combination of the two. + *@param {Model[]|Model|Attributes|Attributes[]} models + *@param {Options} options + */ + add(models: Model[] | Model | Attributes | Attributes[], options: Options): any; + /** + * Remove a model, or a list of models from the set. + * @param {Model|Model[]} models + * @param {Options} options + */ + remove(models: Model | Model[], options: Options): any; + /** + * Update a collection by `set`-ing a new list of models, adding new ones, + * removing models that are no longer present, and merging models that + * already exist in the collection, as necessary. Similar to **Model#set**, + * the core operation for updating the data contained by the collection. + *@param {Model[]|Model|Attributes|Attributes[]} models + * @param {Options} options + */ + set(models: Model[] | Model | Attributes | Attributes[], options: Options): any; + clearStore(options?: {}, filter?: (o: any) => any): Promise; + /** + * When you have more items than you want to add or remove individually, + * you can reset the entire set with a new list of models, without firing + * any granular `add` or `remove` events. Fires `reset` when finished. + * Useful for bulk operations and optimizations. + * @param {Model|Model[]} [models] + * @param {Options} [options] + */ + reset(models?: Model | Model[], options?: Options): Model | Model[]; + /** + * Add a model to the end of the collection. + * @param {Model} model + * @param {Options} [options] + */ + push(model: Model, options?: Options): any; + /** + * Remove a model from the end of the collection. + * @param {Options} [options] + */ + pop(options?: Options): any; + /** + * Add a model to the beginning of the collection. + * @param {Model} model + * @param {Options} [options] + */ + unshift(model: Model, options?: Options): any; + /** + * Remove a model from the beginning of the collection. + * @param {Options} [options] + */ + shift(options?: Options): any; + /** Slice out a sub-array of models from the collection. */ + slice(...args: any[]): any; + /** + * @param {Function|Object} callback + * @param {any} thisArg + */ + filter(callback: Function | any, thisArg: any): any; + /** + * @param {Function} pred + */ + every(pred: Function): any; + /** + * @param {Model[]} values + */ + difference(values: Model[]): any; + max(): any; + min(): any; + drop(n?: number): any; + /** + * @param {Function|Object} pred + */ + some(pred: Function | any): any; + sortBy(iteratee: any): any; + isEmpty(): boolean; + keyBy(iteratee: any): any; + each(callback: any, thisArg: any): any; + forEach(callback: any, thisArg: any): any; + includes(item: any): any; + size(): any; + countBy(f: any): any; + groupBy(pred: any): any; + /** + * @param {number} fromIndex + */ + indexOf(fromIndex: number): any; + /** + * @param {Function|string|RegExp} pred + * @param {number} fromIndex + */ + findLastIndex(pred: Function | string | RegExp, fromIndex: number): any; + /** + * @param {number} fromIndex + */ + lastIndexOf(fromIndex: number): any; + /** + * @param {Function|string|RegExp} pred + */ + findIndex(pred: Function | string | RegExp): any; + last(): any; + head(): any; + first(): any; + map(cb: any, thisArg: any): any; + reduce(callback: any, initialValue: any): any; + reduceRight(callback: any, initialValue: any): any; + toArray(): any[]; + /** + * Get a model from the set by id, cid, model object with id or cid + * properties, or an attributes object that is transformed through modelId. + * @param {string|number|Object|Model} obj + */ + get(obj: string | number | any | Model): any; + /** + * Returns `true` if the model is in the collection. + * @param {string|number|Object|Model} obj + */ + has(obj: string | number | any | Model): boolean; + /** + * Get the model at the given index. + * @param {number} index + */ + at(index: number): any; + /** + * Return models with matching attributes. Useful for simple cases of + * `filter`. + * @param {Attributes} attrs + * @param {boolean} first + */ + where(attrs: Attributes, first: boolean): any; + /** + * Return the first model with matching attributes. Useful for simple cases + * of `find`. + * @param {Attributes} attrs + */ + findWhere(attrs: Attributes): any; + /** + * @param {Attributes} predicate + * @param {number} [fromIndex] + */ + find(predicate: Attributes, fromIndex?: number): any; + /** + * Force the collection to re-sort itself. You don't need to call this under + * normal circumstances, as the set will maintain sort order as each item + * is added. + * @param {Options} options + */ + sort(options: Options): this; + models: any; + /** + * Pluck an attribute from each model in the collection. + * @param {string} attr + */ + pluck(attr: string): any; + /** + * Fetch the default set of models for this collection, resetting the + * collection when they arrive. If `reset: true` is passed, the response + * data will be passed through the `reset` method instead of `set`. + * @param {Options} options + */ + fetch(options: Options): any; + /** + * Create a new instance of a model in this collection. Add the model to the + * collection immediately, unless `wait: true` is passed, in which case we + * wait for the server to agree. + * @param {Model|Attributes} model + * @param {Options} [options] + */ + create(model: Model | Attributes, options?: Options): false | Model | (Promise & { + isResolved: boolean; + isPending: boolean; + isRejected: boolean; + resolve: Function; + reject: Function; + }) | Attributes; + /** + * **parse** converts a response into a list of models to be added to the + * collection. The default implementation is just to pass it through. + * @param {Object} resp + * @param {Options} [options] + */ + parse(resp: any, options?: Options): any; + /** + * Define how to uniquely identify models in the collection. + * @param {Attributes} attrs + */ + modelId(attrs: Attributes): any; + /** Get an iterator of all models in this collection. */ + values(): CollectionIterator; + /** Get an iterator of all model IDs in this collection. */ + keys(): CollectionIterator; + /** Get an iterator of all [ID, model] tuples in this collection. */ + entries(): CollectionIterator; + /** + * Private method to reset all internal state. Called when the collection + * is first initialized or reset. + */ + _reset(): void; + _byId: {}; + /** + * @param {Attributes} attrs + * @param {Options} [options] + */ + createModel(attrs: Attributes, options?: Options): Model; + /** + * Prepare a hash of attributes (or other model) to be added to this + * collection. + * @param {Attributes|Model} attrs + * @param {Options} [options] + * @return {Model} + */ + _prepareModel(attrs: Attributes | Model, options?: Options): Model; + /** + * Internal method called by both remove and set. + * @param {Model[]} models + * @param {Options} [options] + */ + _removeModels(models: Model[], options?: Options): any[]; + /** + * Method for checking whether an object should be considered a model for + * the purposes of adding to the collection. + * @param {any} model + */ + _isModel(model: any): boolean; + /** + * Internal method to create a model's ties to a collection. + * @param {Model} model + * @param {Options} [options] + */ + _addReference(model: Model, options?: Options): void; + /** + * Internal method to sever a model's ties to a collection. + * @private + * @param {Model} model + * @param {Options} [options] + */ + private _removeReference; + /** + * Internal method called every time a model in the set fires an event. + * Sets need to update their indexes when models change ids. All other + * events simply proxy through. "add" and "remove" events that originate + * in other collections are ignored. + * @private + * @param {any} event + * @param {Model} model + * @param {Collection} collection + * @param {Options} [options] + */ + private _onModelEvent; + [Symbol.iterator]: () => CollectionIterator; +} +import { Model } from './model.js'; +declare class CollectionIterator { + /** + * A CollectionIterator implements JavaScript's Iterator protocol, allowing the + * use of `for of` loops in modern browsers and interoperation between + * Collection and other JavaScript functions and third-party libraries + * which can operate on Iterables. + * @param {Collection} collection + * @param {Number} kind + */ + constructor(collection: Collection, kind: number); + _collection: Collection; + _kind: number; + _index: number; + next(): { + value: any; + done: boolean; + }; + [Symbol.iterator](): this; +} +export {}; +//# sourceMappingURL=collection.d.ts.map \ No newline at end of file diff --git a/src/types/collection.d.ts.map b/src/types/collection.d.ts.map new file mode 100644 index 00000000..4d98d124 --- /dev/null +++ b/src/types/collection.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../collection.js"],"names":[],"mappings":"sBAkBa,OAAQ,MAAM,EAAE,GAAG,CAAC;yBACpB,OAAQ,MAAM,EAAE,GAAG,CAAC;sBAEpB,OAAO,cAAc,EAAE,OAAO;gCAE9B,OAAQ,MAAM,EAAE,GAAG,CAAC;;;;sBAc/B;;;;;;WAMG;;;;;;;;;;;;AA1BL;;;;;;;;;GASG;AAEH;;;;;;;GAOG;AACH;IACE;;;;;;OAMG;IACH,oBAHW,KAAK,EAAE,WACP,iBAAiB,kBAa3B;IAPoB,YAA2B;IACR,gBAAoC;IAQ5E;;OAEG;IACH,wDAEC;IAED;;OAEG;IACH,qDAEC;IARC,gDAA8B;IAmBhC;;OAEG;IACH,6BAEC;IAdD;;;;OAIG;IACH,0BAEC;IASD,kBAEC;IAED;;;OAGG;IACH,sBAAkB;IAElB;;;OAGG;IACH,mBAAe;IAEf;;;;OAIG;IACH,gBAFU,OAAO,OAMhB;IAED;;;;OAIG;IACH,aAJU,MAAM,SACN,KAAK,GAAC,UAAU,WAChB,OAAO,OAIhB;IAED;;;;;;OAMG;IACH,YAHU,KAAK,EAAE,GAAC,KAAK,GAAC,UAAU,GAAC,UAAU,EAAE,WACrC,OAAO,OAIhB;IAED;;;;OAIG;IACH,eAHW,KAAK,GAAC,KAAK,EAAE,WACb,OAAO,OAYjB;IAED;;;;;;;OAOG;IACH,YAHU,KAAK,EAAE,GAAC,KAAK,GAAC,UAAU,GAAC,UAAU,EAAE,WACpC,OAAO,OAkHjB;IAED,kEAkBC;IAED;;;;;;;OAOG;IACH,eAHW,KAAK,GAAC,KAAK,EAAE,YACb,OAAO,mBAYjB;IAED;;;;OAIG;IACH,YAHW,KAAK,YACL,OAAO,OAIjB;IAED;;;OAGG;IACH,cAFW,OAAO,OAKjB;IAED;;;;OAIG;IACH,eAHW,KAAK,YACL,OAAO,OAIjB;IAED;;;OAGG;IACH,gBAFW,OAAO,OAKjB;IAED,2DAA2D;IAC3D,2BAEC;IAED;;;OAGG;IACH,iBAHW,cAAe,WACf,GAAG,OAIb;IAED;;OAEG;IACH,2BAMC;IAED;;OAEG;IACH,mBAFW,KAAK,EAAE,OAIjB;IAED,WAEC;IAED,WAEC;IAED,sBAEC;IAED;;OAEG;IACH,WAFW,cAAe,OAQzB;IAED,2BAKC;IAED,mBAEC;IAED,0BAEC;IAED,uCAEC;IAED,0CAEC;IAED,yBAEC;IAED,YAEC;IAED,qBAEC;IAED,wBAEC;IAED;;OAEG;IACH,mBAFW,MAAM,OAIhB;IAED;;;OAGG;IACH,oBAHW,WAAS,MAAM,GAAC,MAAM,aACtB,MAAM,OAOhB;IAED;;OAEG;IACH,uBAFW,MAAM,OAIhB;IAED;;OAEG;IACH,gBAFW,WAAS,MAAM,GAAC,MAAM,OAIhC;IAED,YAGC;IAED,YAEC;IAED,aAEC;IAED,gCAEC;IAED,8CAEC;IAED,mDAEC;IAED,iBAEC;IAED;;;;OAIG;IACH,SAFW,MAAM,GAAC,MAAM,SAAQ,KAAK,OASpC;IAED;;;OAGG;IACH,SAFW,MAAM,GAAC,MAAM,SAAQ,KAAK,WAIpC;IAED;;;OAGG;IACH,UAFW,MAAM,OAKhB;IAED;;;;;OAKG;IACH,aAHW,UAAU,SACV,OAAO,OAIjB;IAED;;;;OAIG;IACH,iBAFW,UAAU,OAIpB;IAED;;;OAGG;IACH,gBAHW,UAAU,cACV,MAAM,OAKhB;IAED;;;;;OAKG;IACH,cAFW,OAAO,QAkBjB;IANG,YAAqC;IAQzC;;;OAGG;IACH,YAFW,MAAM,OAIhB;IAED;;;;;OAKG;IACH,eAFW,OAAO,OAiBjB;IAED;;;;;;OAMG;IACH,cAHW,KAAK,GAAC,UAAU,YAChB,OAAO;;;;;;oBAqCjB;IAED;;;;;OAKG;IACH,2BAFW,OAAO,OAIjB;IAED;;;OAGG;IACH,eAFW,UAAU,OAIpB;IAED,wDAAwD;IACxD,6BAEC;IAED,2DAA2D;IAC3D,2BAEC;IAED,oEAAoE;IACpE,8BAEC;IAED;;;OAGG;IACH,eAGC;IADC,UAAe;IAGjB;;;OAGG;IACH,mBAHW,UAAU,YACV,OAAO,SAKjB;IAED;;;;;;OAMG;IACH,qBAJW,UAAU,GAAC,KAAK,YAChB,OAAO,GACN,KAAK,CAahB;IAED;;;;OAIG;IACH,sBAHW,KAAK,EAAE,YACP,OAAO,SA0BjB;IAED;;;;OAIG;IACH,gBAFW,GAAG,WAIb;IAED;;;;OAIG;IACH,qBAHW,KAAK,YACL,OAAO,QAOjB;IAED;;;;;OAKG;IACH,yBAMC;IAED;;;;;;;;;;OAUG;IACH,sBAcC;IAjwBC,4CAAmC;CAkwBtC;sBAvzBqB,YAAY;AAg0BlC;IACE;;;;;;;OAOG;IACH,wBAHW,UAAU,gBAOpB;IAHC,wBAA6B;IAC7B,cAAiB;IACjB,eAAe;IAGjB;;;MA6BC;IAED,0BAEC;CACF"} \ No newline at end of file diff --git a/src/types/drivers/sessionStorage.d.ts b/src/types/drivers/sessionStorage.d.ts new file mode 100644 index 00000000..148fcc26 --- /dev/null +++ b/src/types/drivers/sessionStorage.d.ts @@ -0,0 +1,26 @@ +export default sessionStorageWrapper; +declare namespace sessionStorageWrapper { + export let _driver: string; + export { _initStorage }; + export let _support: boolean; + export { iterate }; + export { getItem }; + export { setItem }; + export { removeItem }; + export { clear }; + export { length }; + export { key }; + export { keys }; + export { dropInstance }; +} +declare function _initStorage(options: any): void; +declare function iterate(iterator: any, callback: any): any; +declare function getItem(key: any, callback: any): any; +declare function setItem(key: any, value: any, callback: any): Promise; +declare function removeItem(key: any, callback: any): any; +declare function clear(callback: any): any; +declare function length(callback: any): any; +declare function key(n: any, callback: any): any; +declare function keys(callback: any): any; +declare function dropInstance(options: any, callback: any, ...args: any[]): Promise; +//# sourceMappingURL=sessionStorage.d.ts.map \ No newline at end of file diff --git a/src/types/drivers/sessionStorage.d.ts.map b/src/types/drivers/sessionStorage.d.ts.map new file mode 100644 index 00000000..ea3b4d86 --- /dev/null +++ b/src/types/drivers/sessionStorage.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"sessionStorage.d.ts","sourceRoot":"","sources":["../../drivers/sessionStorage.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AA0DA,kDAQC;AA2CD,4DA0CC;AA7DD,uDAgBC;AAkHD,6EA2BC;AAxCD,0DAOC;AAhJD,2CAeC;AA+GD,4CAQC;AAlDD,iDAoBC;AAED,0CAiBC;AAwDD,0FAiCC"} \ No newline at end of file diff --git a/src/types/element.d.ts b/src/types/element.d.ts new file mode 100644 index 00000000..ebd0721e --- /dev/null +++ b/src/types/element.d.ts @@ -0,0 +1,111 @@ +export default ElementView; +declare const ElementView_base: { + new (...args: any[]): { + on(name: string, callback: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any, context: any): any; + _events: any; + _listeners: {}; + listenTo(obj: any, name: string, callback?: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any): any; + _listeningTo: {}; + _listenId: any; + off(name: string, callback: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any, context?: any): any; + stopListening(obj?: any, name?: string, callback?: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any): any; + once(name: string, callback: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any, context: any): any; + listenToOnce(obj: any, name: string, callback?: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any): any; + trigger(name: string, ...args: any[]): any; + }; +} & { + new (): HTMLElement; + prototype: HTMLElement; +}; +declare class ElementView extends ElementView_base { + /** + * @param {Options} options + */ + constructor(options?: Record); + /** + * @typedef {import('./model.js').Model} Model + * @typedef {import('./collection.js').Collection} Collection + * @typedef {Record.} Options + * + * @callback EventCallback + * @param {any} event + * @param {Model} model + * @param {Collection} collection + * @param {Options} [options] + */ + set events(arg: {}); + get events(): {}; + _declarativeEvents: {}; + stopListening: any; + cid: any; + _domEvents: any[]; + createRenderRoot(): this; + connectedCallback(...args: any[]): void; + _initialized: boolean; + disconnectedCallback(): void; + /** + * preinitialize is an empty function by default. You can override it with a function + * or object. preinitialize will run before any instantiation logic is run in the View + * eslint-disable-next-line class-methods-use-this + */ + preinitialize(): void; + /** + * Initialize is an empty function by default. Override it with your own + * initialization logic. + */ + initialize(): void; + beforeRender(): void; + afterRender(): void; + /** + * **render** is the core function that your view should override, in order + * to populate its element (`this.el`), with the appropriate HTML. The + * convention is for **render** to always return `this`. + */ + render(): this; + toHTML(): string; + /** + * Set callbacks, where `this.events` is a hash of + * + * *{"event selector": "callback"}* + * + * { + * 'mousedown .title': 'edit', + * 'click .button': 'save', + * 'click .open': function(e) { ... } + * } + * + * pairs. Callbacks will be bound to the view, with `this` set properly. + * Uses event delegation for efficiency. + * Omitting the selector binds the event to `this.el`. + */ + delegateEvents(): this; + /** + * Make a event delegation handler for the given `eventName` and `selector` + * and attach it to `this.el`. + * If selector is empty, the listener will be bound to `this.el`. If not, a + * new handler that will recursively traverse up the event target's DOM + * hierarchy looking for a node that matches the selector. If one is found, + * the event's `delegateTarget` property is set to it and the return the + * result of calling bound `listener` with the parameters given to the + * handler. + * @param {string} eventName + * @param {string} selector + * @param {(ev: Event) => any} listener + */ + delegate(eventName: string, selector: string, listener: (ev: Event) => any): this | ((ev: Event) => any); + /** + * Clears all callbacks previously bound to the view by `delegateEvents`. + * You usually don't need to use this, but may wish to if you have multiple + * Backbone views attached to the same DOM element. + */ + undelegateEvents(): this; + /** + * A finer-grained `undelegateEvents` for removing a single delegated event. + * `selector` and `listener` are both optional. + * @param {string} eventName + * @param {string} selector + * @param {(ev: Event) => any} listener + */ + undelegate(eventName: string, selector: string, listener: (ev: Event) => any): this; +} +//# sourceMappingURL=element.d.ts.map \ No newline at end of file diff --git a/src/types/element.d.ts.map b/src/types/element.d.ts.map new file mode 100644 index 00000000..876caef8 --- /dev/null +++ b/src/types/element.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../element.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAOA;IAsBE;;OAEG;IACH,2CAeC;IAtCD;;;;;;;;;;OAUG;IAEH,oBAEC;IAED,iBAEC;IALC,uBAAgC;IAchC,mBAAyB;IAIzB,SAA2B;IAE3B,kBAAoB;IAOtB,yBAGC;IAED,wCAOC;IAHG,sBAAwB;IAK5B,6BAGC;IAED;;;;OAIG;IACH,sBAAkB;IAElB;;;OAGG;IACH,mBAAe;IAEf,qBAAiB;IACjB,oBAAgB;IAEhB;;;;OAIG;IACH,eAKC;IAED,iBAEC;IAED;;;;;;;;;;;;;;OAcG;IACH,uBAaC;IAED;;;;;;;;;;;;OAYG;IACH,oBAJW,MAAM,YACN,MAAM,iBACD,KAAK,KAAK,GAAG,gBAAb,KAAK,KAAK,GAAG,EAsC5B;IAED;;;;OAIG;IACH,yBASC;IAED;;;;;;OAMG;IACH,sBAJW,MAAM,YACN,MAAM,iBACD,KAAK,KAAK,GAAG,QAyB5B;CACF"} \ No newline at end of file diff --git a/src/types/eventemitter.d.ts b/src/types/eventemitter.d.ts new file mode 100644 index 00000000..8c607c9c --- /dev/null +++ b/src/types/eventemitter.d.ts @@ -0,0 +1,93 @@ +/** + * @function + * @template {new(...args: any[]) => {}} ClassConstructor + * @param {ClassConstructor} Base + */ +export function EventEmitter {}>(Base: ClassConstructor): { + new (...args: any[]): { + /** + * @typedef {import('./model.js').Model} Model + * @typedef {import('./collection.js').Collection} Collection + * @typedef {Record.} Options + * + * @callback EventCallback + * @param {any} event + * @param {Model} model + * @param {Collection} collection + * @param {Options} [options] + */ + /** + * Bind an event to a `callback` function. Passing `"all"` will bind + * the callback to all events fired. + * @param {string} name + * @param {EventCallback} callback + * @param {any} context + * @return {EventEmitter} + */ + on(name: string, callback: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any, context: any): any; + _events: any; + _listeners: {}; + /** + * Inversion-of-control versions of `on`. Tell *this* object to listen to + * an event in another object... keeping track of what it's listening to + * for easier unbinding later. + * @param {any} obj + * @param {string} name + * @param {EventCallback} [callback] + * @return {EventEmitter} + */ + listenTo(obj: any, name: string, callback?: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any): any; + _listeningTo: {}; + _listenId: any; + /** + * Remove one or many callbacks. If `context` is null, removes all + * callbacks with that function. If `callback` is null, removes all + * callbacks for the event. If `name` is null, removes all bound + * callbacks for all events. + * @param {string} name + * @param {EventCallback} callback + * @param {any} [context] + * @return {EventEmitter} + */ + off(name: string, callback: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any, context?: any): any; + /** + * Tell this object to stop listening to either specific events ... or + * to every object it's currently listening to. + * @param {any} [obj] + * @param {string} [name] + * @param {EventCallback} [callback] + * @return {EventEmitter} + */ + stopListening(obj?: any, name?: string, callback?: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any): any; + /** + * Bind an event to only be triggered a single time. After the first time + * the callback is invoked, its listener will be removed. If multiple events + * are passed in using the space-separated syntax, the handler will fire + * once for each event, not once for a combination of all events. + * @param {string} name + * @param {EventCallback} callback + * @param {any} context + * @return {EventEmitter} + */ + once(name: string, callback: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any, context: any): any; + /** + * Inversion-of-control versions of `once`. + * @param {any} obj + * @param {string} name + * @param {EventCallback} [callback] + * @return {EventEmitter} + */ + listenToOnce(obj: any, name: string, callback?: (event: any, model: import("./model.js").Model, collection: import("./collection.js").Collection, options?: Record) => any): any; + /** + * Trigger one or many events, firing all bound callbacks. Callbacks are + * passed the same arguments as `trigger` is, apart from the event name + * (unless you're listening on `"all"`, which will cause your callback to + * receive the true name of the event as the first argument). + * @param {string} name + * @return {EventEmitter} + */ + trigger(name: string, ...args: any[]): any; + }; +} & ClassConstructor; +export default EventEmitter; +//# sourceMappingURL=eventemitter.d.ts.map \ No newline at end of file diff --git a/src/types/eventemitter.d.ts.map b/src/types/eventemitter.d.ts.map new file mode 100644 index 00000000..acf6bfda --- /dev/null +++ b/src/types/eventemitter.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"eventemitter.d.ts","sourceRoot":"","sources":["../eventemitter.js"],"names":[],"mappings":"AAaA;;;;GAIG;AACH,oEAH2B,GAAG,EAAE,KAAK,EAAE;kBAAZ,GAAG,EAAE;QAK5B;;;;;;;;;;WAUG;QAEH;;;;;;;WAOG;iBAJQ,MAAM,oBATN,GAAG,uIAWH,GAAG;;;QAqBd;;;;;;;;WAQG;sBAJQ,GAAG,QACH,MAAM,qBArCN,GAAG;;;QAiEd;;;;;;;;;WASG;kBAJQ,MAAM,oBAtEN,GAAG,wIAwEH,GAAG;QAad;;;;;;;WAOG;4BAJQ,GAAG,SACH,MAAM,qBAzFN,GAAG;QAiHd;;;;;;;;;WASG;mBAJQ,MAAM,oBAtHN,GAAG,uIAwHH,GAAG;QAUd;;;;;;WAMG;0BAJQ,GAAG,QACH,MAAM,qBArIN,GAAG;QA+Id;;;;;;;WAOG;sBAFQ,MAAM;;qBAUpB"} \ No newline at end of file diff --git a/src/types/helpers.d.ts b/src/types/helpers.d.ts new file mode 100644 index 00000000..124dfb77 --- /dev/null +++ b/src/types/helpers.d.ts @@ -0,0 +1,62 @@ +export function guid(): string; +export function inherits(protoProps: any, staticProps: any): any; +export function getResolveablePromise(): Promise & { + isResolved: boolean; + isPending: boolean; + isRejected: boolean; + resolve: Function; + reject: Function; +}; +export function urlError(): void; +export function wrapError(model: any, options: any): void; +/** + * @typedef {import('./model.js').Model} Model + * @typedef {import('./collection.js').Collection} Collection + */ +/** + * @param {Model | Collection} model + */ +export function getSyncMethod(model: Model | Collection): any; +/** + * @typedef {Object} SyncOptions + * @property {string} [url] + * @property {any} [data] + * @property {any} [attrs] + * @property {Function} [success] + * @property {Function} [error] + * @property {any} [xhr] + */ +/** + * Override this function to change the manner in which Backbone persists + * models to the server. You will be passed the type of request, and the + * model in question. By default makes a `fetch()` API call + * to the model's `url()`. + * + * Some possible customizations could be: + * + * - Use `setTimeout` to batch rapid-fire updates into a single request. + * - Persist models via WebSockets instead of Ajax. + * - Persist models to browser storage + * + * @param {'create'|'update'|'patch'} method + * @param {import('./model.js').Model} model + * @param {SyncOptions} [options] + */ +export function sync(method: 'create' | 'update' | 'patch', model: import('./model.js').Model, options?: SyncOptions): Promise; +/** + * Custom error for indicating timeouts + * @namespace _converse + */ +export class NotImplementedError extends Error { +} +export type Model = import('./model.js').Model; +export type Collection = import('./collection.js').Collection; +export type SyncOptions = { + url?: string; + data?: any; + attrs?: any; + success?: Function; + error?: Function; + xhr?: any; +}; +//# sourceMappingURL=helpers.d.ts.map \ No newline at end of file diff --git a/src/types/helpers.d.ts.map b/src/types/helpers.d.ts.map new file mode 100644 index 00000000..da81755f --- /dev/null +++ b/src/types/helpers.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../helpers.js"],"names":[],"mappings":"AAkBA,+BAGC;AASD,iEA6BC;AAED;gBAGgB,OAAO;eACP,OAAO;gBACP,OAAO;;;EAwCtB;AAGD,iCAEC;AAGD,0DAMC;AAWD;;;GAGG;AAGH;;GAEG;AACH,qCAFW,KAAK,GAAG,UAAU,OAK5B;AAED;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,6BAJW,QAAQ,GAAC,QAAQ,GAAC,OAAO,SACzB,OAAO,YAAY,EAAE,KAAK,YAC1B,WAAW,qBAsBrB;AAxLD;;;GAGG;AACH;CAAiD;oBAyHpC,OAAO,YAAY,EAAE,KAAK;yBAC1B,OAAO,iBAAiB,EAAE,UAAU;;UAcnC,MAAM;WACN,GAAG;YACH,GAAG;;;UAGH,GAAG"} \ No newline at end of file diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 00000000..c605551c --- /dev/null +++ b/src/types/index.d.ts @@ -0,0 +1,18 @@ +export default skeletor; +declare namespace skeletor { + let VERSION: string; + function noConflict(): { + Collection: typeof Collection; + ElementView: typeof ElementView; + EventEmitter: typeof EventEmitter; + Model: typeof Model; + sync: typeof sync; + }; +} +import { Collection } from './collection.js'; +import ElementView from './element.js'; +import EventEmitter from './eventemitter.js'; +import { Model } from './model.js'; +import { sync } from './helpers.js'; +export { Collection, ElementView, EventEmitter, Model, sync }; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/src/types/index.d.ts.map b/src/types/index.d.ts.map new file mode 100644 index 00000000..820e720e --- /dev/null +++ b/src/types/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.js"],"names":[],"mappings":";;;IA8BA;;;;;;MAGC;;2BA9B0B,iBAAiB;wBAFpB,cAAc;yBACb,mBAAmB;sBAEtB,YAAY;qBACb,cAAc"} \ No newline at end of file diff --git a/src/types/listening.d.ts b/src/types/listening.d.ts new file mode 100644 index 00000000..f9540633 --- /dev/null +++ b/src/types/listening.d.ts @@ -0,0 +1,40 @@ +export default Listening; +/** + * A listening class that tracks and cleans up memory bindings + * when all callbacks have been offed. + */ +declare class Listening { + /** @typedef {import('./eventemitter.js').default} EventEmitter */ + /** + * @param {any} listener + * @param {any} obj + */ + constructor(listener: any, obj: any); + id: any; + listener: any; + obj: any; + interop: boolean; + count: number; + _events: any; + /** + * @param {string} name + * @param {Function} callback + * @param {any} context + * @param {Listening} _listening + */ + start(name: string, callback: Function, context: any, _listening: Listening): this; + /** + * Stop's listening to a callback (or several). + * Uses an optimized counter if the listenee uses Backbone.Events. + * Otherwise, falls back to manual tracking to support events + * library interop. + * @param {string} name + * @param {Function} callback + */ + stop(name: string, callback: Function): void; + /** + * Cleans up memory bindings between the listener and the listenee. + */ + cleanup(): void; +} +//# sourceMappingURL=listening.d.ts.map \ No newline at end of file diff --git a/src/types/listening.d.ts.map b/src/types/listening.d.ts.map new file mode 100644 index 00000000..346d0430 --- /dev/null +++ b/src/types/listening.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"listening.d.ts","sourceRoot":"","sources":["../listening.js"],"names":[],"mappings":";AAEA;;;GAGG;AACH;IAEE,kEAAkE;IAElE;;;OAGG;IACH,sBAHW,GAAG,OACH,GAAG,EASb;IANC,QAA4B;IAC5B,cAAwB;IACxB,SAAc;IACd,iBAAmB;IACnB,cAAc;IACd,aAAwB;IAG1B;;;;;OAKG;IACH,YALW,MAAM,+BAEN,GAAG,cACH,SAAS,QAmBnB;IAED;;;;;;;OAOG;IACH,WAHW,MAAM,4BAgBhB;IAED;;OAEG;IACH,gBAGC;CACF"} \ No newline at end of file diff --git a/src/types/model.d.ts b/src/types/model.d.ts new file mode 100644 index 00000000..1d43047b --- /dev/null +++ b/src/types/model.d.ts @@ -0,0 +1,233 @@ +export type Collection = import('./collection.js').Collection; +export type Attributes = Record; +export type Options = Record; +export type ModelOptions = Record; +declare const Model_base: { + new (...args: any[]): { + /** + * @typedef {import('./collection.js').Collection} Collection + * @typedef {Record.} Attributes + * + * @typedef {Record.} Options + * @property {boolean} [validate] + * + * @typedef {Record.} ModelOptions + * @property {Collection} [collection] + * @property {boolean} [parse] + * @property {boolean} [unset] + * @property {boolean} [silent] + */ + /** + * **Models** are the basic data object in the framework -- + * frequently representing a row in a table in a database on your server. + * A discrete chunk of data and a bunch of useful, related methods for + * performing computations and transformations on that data. + */ + on(name: string, callback: (event: any, model: Model, collection: import("./collection.js").Collection, options: Record) => any, context: any): any; + _events: any; + _listeners: {}; + listenTo(obj: any, name: string, callback?: (event: any, model: Model, collection: import("./collection.js").Collection, options: Record) => any): any; + _listeningTo: {}; + _listenId: any; + off(name: string, callback: (event: any, model: Model, collection: import("./collection.js").Collection, options: Record) => any, context?: any): any; + stopListening(obj?: any, name?: string, callback?: (event: any, model: Model, collection: import("./collection.js").Collection, options: Record) => any): any; + once(name: string, callback: (event: any, model: Model, collection: import("./collection.js").Collection, options: Record) => any, context: any): any; + listenToOnce(obj: any, name: string, callback?: (event: any, model: Model, collection: import("./collection.js").Collection, options: Record) => any): any; + trigger(name: string, ...args: any[]): any; + }; +} & ObjectConstructor; +/** + * @typedef {import('./collection.js').Collection} Collection + * @typedef {Record.} Attributes + * + * @typedef {Record.} Options + * @property {boolean} [validate] + * + * @typedef {Record.} ModelOptions + * @property {Collection} [collection] + * @property {boolean} [parse] + * @property {boolean} [unset] + * @property {boolean} [silent] + */ +/** + * **Models** are the basic data object in the framework -- + * frequently representing a row in a table in a database on your server. + * A discrete chunk of data and a bunch of useful, related methods for + * performing computations and transformations on that data. + */ +export class Model extends Model_base { + /** + * Create a new model with the specified attributes. A client id (`cid`) + * is automatically generated and assigned for you. + * @param {Attributes} attributes + * @param {ModelOptions} options + */ + constructor(attributes: Attributes, options: ModelOptions, ...args: any[]); + cid: any; + attributes: {}; + validationError: any; + validate: any; + collection: any; + changed: {}; + /** + * The default name for the JSON `id` attribute is `"id"`. MongoDB and + * CouchDB users may want to set this to `"_id"` (by overriding this getter + * in a subclass). + */ + get idAttribute(): string; + /** + * The prefix is used to create the client id which is used to identify models locally. + * You may want to override this if you're experiencing name clashes with model ids. + */ + get cidPrefix(): string; + /** + * preinitialize is an empty function by default. You can override it with a function + * or object. preinitialize will run before any instantiation logic is run in the Model. + */ + preinitialize(): void; + /** + * Initialize is an empty function by default. Override it with your own + * initialization logic. + */ + initialize(): void; + /** + * Return a copy of the model's `attributes` object. + */ + toJSON(): any; + /** + * Override this if you need custom syncing semantics for *this* particular model. + * @param {'create'|'update'|'patch'|'delete'|'read'} method + * @param {Model} model + * @param {Options} options + */ + sync(method: 'create' | 'update' | 'patch' | 'delete' | 'read', model: Model, options: Options): any; + /** + * Get the value of an attribute. + * @param {string} attr + */ + get(attr: string): any; + keys(): string[]; + values(): any[]; + pairs(): [string, any][]; + entries(): [string, any][]; + invert(): any; + pick(...args: any[]): any; + omit(...args: any[]): any; + isEmpty(): any; + /** + * Returns `true` if the attribute contains a value that is not null + * or undefined. + * @param {string} attr + */ + has(attr: string): boolean; + /** + * Special-cased proxy to lodash's `matches` method. + * @param {Attributes} attrs + */ + matches(attrs: Attributes): boolean; + /** + * Set a hash of model attributes on the object, firing `"change"`. This is + * the core primitive operation of a model, updating the data and notifying + * anyone who needs to know about the change in state. The heart of the beast. + * @param {string|Object} key + * @param {string|Object} val + * @param {Options} [options] + */ + set(key: string | any, val: string | any, options?: Options): false | this; + _changing: boolean; + _previousAttributes: any; + id: any; + _pending: boolean | Options; + /** + * Remove an attribute from the model, firing `"change"`. `unset` is a noop + * if the attribute doesn't exist. + * @param {string} attr + * @param {Options} options + */ + unset(attr: string, options: Options): false | this; + /** + * Clear all attributes on the model, firing `"change"`. + * @param {Options} options + */ + clear(options: Options): false | this; + /** + * Determine if the model has changed since the last `"change"` event. + * If you specify an attribute name, determine if that attribute has changed. + * @param {string} [attr] + */ + hasChanged(attr?: string): any; + /** + * Return an object containing all the attributes that have changed, or + * false if there are no changed attributes. Useful for determining what + * parts of a view need to be updated and/or what attributes need to be + * persisted to the server. Unset attributes will be set to undefined. + * You can also pass an attributes object to diff against the model, + * determining if there *would be* a change. + * @param {Object} diff + */ + changedAttributes(diff: any): any; + /** + * Get the previous value of an attribute, recorded at the time the last + * `"change"` event was fired. + * @param {string} [attr] + */ + previous(attr?: string): any; + /** + * Get all of the attributes of the model at the time of the previous + * `"change"` event. + */ + previousAttributes(): any; + /** + * Fetch the model from the server, merging the response with the model's + * local attributes. Any changed attributes will trigger a "change" event. + * @param {Options} options + */ + fetch(options: Options): any; + /** + * Set a hash of model attributes, and sync the model to the server. + * If the server returns an attributes hash that differs, the model's + * state will be `set` again. + * @param {string|Attributes} key + * @param {string|Options} [val] + * @param {Options} [options] + */ + save(key: string | Attributes, val?: string | Options, options?: Options): any; + /** + * Destroy this model on the server if it was already persisted. + * Optimistically removes the model from its collection, if it has one. + * If `wait: true` is passed, waits for the server to respond before removal. + * @param {Options} [options] + */ + destroy(options?: Options): boolean; + /** + * Default URL for the model's representation on the server -- if you're + * using Backbone's restful methods, override this to change the endpoint + * that will be called. + */ + url(): any; + /** + * **parse** converts a response into the hash of attributes to be `set` on + * the model. The default implementation is just to pass the response along. + * @param {Options} resp + * @param {Options} [options] + */ + parse(resp: Options, options?: Options): Options; + /** + * A model is new if it has never been saved to the server, and lacks an id. + */ + isNew(): boolean; + /** + * Check if the model is currently in a valid state. + * @param {Options} [options] + */ + isValid(options?: Options): boolean; + /** + * Run validation against the next complete set of model attributes, + * returning `true` if all is well. Otherwise, fire an `"invalid"` event. + * @param {Attributes} attrs + * @param {Options} [options] + */ + _validate(attrs: Attributes, options?: Options): boolean; +} +export {}; +//# sourceMappingURL=model.d.ts.map \ No newline at end of file diff --git a/src/types/model.d.ts.map b/src/types/model.d.ts.map new file mode 100644 index 00000000..456bcd03 --- /dev/null +++ b/src/types/model.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../model.js"],"names":[],"mappings":"yBAgBa,OAAO,iBAAiB,EAAE,UAAU;yBACpC,OAAQ,MAAM,EAAE,GAAG,CAAC;sBAEpB,OAAQ,MAAM,EAAE,GAAG,CAAC;2BAGpB,OAAQ,MAAM,EAAE,GAAG,CAAC;;;QAPjC;;;;;;;;;;;;WAYG;QAEH;;;;;WAKG;;;;;;;;;;;;;;AAnBH;;;;;;;;;;;;GAYG;AAEH;;;;;GAKG;AACH;IACE;;;;;OAKG;IACH,wBAHW,UAAU,WACV,YAAY,kBA2BtB;IApBC,SAAmC;IACnC,eAAoB;IAGpB,qBAA2B;IAE3B,cAAqC;IAEb,gBAAoC;IAW5D,YAAiB;IAGnB;;;;OAIG;IAEH,0BAEC;IAED;;;OAGG;IAEH,wBAEC;IAED;;;OAGG;IAEH,sBAAkB;IAElB;;;OAGG;IAEH,mBAAe;IAEf;;OAEG;IACH,cAEC;IAED;;;;;OAKG;IAEH,aALW,QAAQ,GAAC,QAAQ,GAAC,OAAO,GAAC,QAAQ,GAAC,MAAM,SACzC,KAAK,WACL,OAAO,OAKjB;IAED;;;OAGG;IACH,UAFW,MAAM,OAIhB;IAED,iBAEC;IAED,gBAEC;IAED,yBAEC;IAED,2BAEC;IAED,cAEC;IAED,0BAKC;IAED,0BAKC;IAED,eAEC;IAED;;;;OAIG;IACH,UAFW,MAAM,WAIhB;IAED;;;OAGG;IACH,eAFW,UAAU,WAIpB;IAED;;;;;;;OAOG;IACH,SAJW,MAAM,MAAO,OACb,MAAM,MAAO,YACb,OAAO,gBAuEjB;IA/CC,mBAAqB;IAGnB,yBAAiD;IAqBpB,QAAoC;IAI7C,4BAAuB;IAqB/C;;;;;OAKG;IACH,YAHW,MAAM,WACN,OAAO,gBAIjB;IAED;;;OAGG;IACH,eAFW,OAAO,gBAMjB;IAED;;;;OAIG;IACH,kBAFW,MAAM,OAKhB;IAED;;;;;;;;OAQG;IACH,kCAeC;IAED;;;;OAIG;IACH,gBAFW,MAAM,OAKhB;IAED;;;OAGG;IACH,0BAEC;IAED;;;;OAIG;IACH,eAFW,OAAO,OAgBjB;IAED;;;;;;;OAOG;IACH,UAJW,MAAM,GAAC,UAAU,QACjB,MAAM,GAAC,OAAO,YACd,OAAO,OAiEjB;IAED;;;;;OAKG;IACH,kBAFW,OAAO,WA2BjB;IAED;;;;OAIG;IACH,WAKC;IAED;;;;;OAKG;IACH,YAHW,OAAO,YACP,OAAO,WAIjB;IAED;;OAEG;IACH,iBAEC;IAED;;;OAGG;IACH,kBAFW,OAAO,WAIjB;IAED;;;;;OAKG;IACH,iBAHW,UAAU,YACV,OAAO,WASjB;CACF"} \ No newline at end of file diff --git a/src/types/storage.d.ts b/src/types/storage.d.ts new file mode 100644 index 00000000..a39bb9b1 --- /dev/null +++ b/src/types/storage.d.ts @@ -0,0 +1,34 @@ +export default Storage; +declare class Storage { + constructor(id: any, type: any, batchedWrites?: boolean); + storeInitialized: Promise; + store: any; + name: any; + /** + * @param {'local'|'session'|'indexed'|'in_memory'} type + * @param {boolean} batchedWrites + */ + initStore(type: 'local' | 'session' | 'indexed' | 'in_memory', batchedWrites: boolean): Promise; + flush(): any; + clear(): Promise; + sync(): { + (method: any, model: any, options: any): Promise; + __name__: string; + }; + removeCollectionReference(model: any, collection: any): any; + addCollectionReference(model: any, collection: any): any; + getCollectionReferenceData(model: any): {}; + save(model: any): Promise; + create(model: any, options: any): Promise; + update(model: any): Promise; + find(model: any): any; + findAll(): Promise; + destroy(model: any, collection: any): Promise; + getStorageSize(): any; + getItemName(id: any): string; +} +declare namespace Storage { + export let sessionStorageInitialized: any; + export { localForage }; +} +//# sourceMappingURL=storage.d.ts.map \ No newline at end of file diff --git a/src/types/storage.d.ts.map b/src/types/storage.d.ts.map new file mode 100644 index 00000000..f2cd3615 --- /dev/null +++ b/src/types/storage.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../storage.js"],"names":[],"mappings":";AAkBA;IACE,yDAgBC;IATG,gCAA2D;IAE3D,WAAiB;IAMnB,UAAc;IAGhB;;;OAGG;IACH,gBAHW,OAAO,GAAC,SAAS,GAAC,SAAS,GAAC,WAAW,iBACvC,OAAO,iBAgBjB;IAED,aAEC;IAED,uBAMC;IAED;;;MAiFC;IAED,4DAOC;IAED,yDAUC;IAED,2CAYC;IAED,+BAYC;IAED,+CASC;IAED,iCAEC;IAED,sBAEC;IAED,0BASC;IAED,mDAKC;IAED,sBAEC;IAED,6BAEC;CACF"} \ No newline at end of file diff --git a/src/types/utils/events.d.ts b/src/types/utils/events.d.ts new file mode 100644 index 00000000..308b14fd --- /dev/null +++ b/src/types/utils/events.d.ts @@ -0,0 +1,28 @@ +/** + * Iterates over the standard `event, callback` (as well as the fancy multiple + * space-separated events `"change blur", callback` and jQuery-style event + * maps `{event: callback}`). + */ +export function eventsApi(iteratee: any, events: any, name: any, callback: any, opts: any): any; +export function onApi(events: any, name: any, callback: any, options: any): any; +/** + * An try-catch guarded #on function, to prevent poisoning the global + * `_listening` variable. + * @param {any} obj + * @param {string} name + * @param {Function} callback + * @param {any} context + */ +export function tryCatchOn(obj: any, name: string, callback: Function, context: any): any; +/** + * The reducing API that removes a callback from the `events` object. + */ +export function offApi(events: any, name: any, callback: any, options: any): any; +/** + * Reduces the event callbacks into a map of `{event: onceWrapper}`. + * `offer` unbinds the `onceWrapper` after it has been called. + */ +export function onceMap(map: any, name: any, callback: any, offer: any): any; +/** Handles triggering the appropriate event callbacks. */ +export function triggerApi(objEvents: any, name: any, callback: any, args: any): any; +//# sourceMappingURL=events.d.ts.map \ No newline at end of file diff --git a/src/types/utils/events.d.ts.map b/src/types/utils/events.d.ts.map new file mode 100644 index 00000000..6bfe66e0 --- /dev/null +++ b/src/types/utils/events.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../utils/events.js"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,gGAmBC;AAGD,gFAWC;AAED;;;;;;;GAOG;AACH,gCALW,GAAG,QACH,MAAM,+BAEN,GAAG,OAQb;AAED;;GAEG;AACH,iFAkDC;AAED;;;GAGG;AACH,6EASC;AAED,0DAA0D;AAC1D,qFASC"} \ No newline at end of file diff --git a/test/collection.js b/test/collection.js index 892f71ea..387810f4 100644 --- a/test/collection.js +++ b/test/collection.js @@ -1894,12 +1894,7 @@ }); QUnit.test('Collection implements Iterable, values is default iterator function', function (assert) { - const $$iterator = typeof Symbol === 'function' && Symbol.iterator; - // This test only applies to environments which define Symbol.iterator. - if (!$$iterator) { - assert.expect(0); - return; - } + const $$iterator = Symbol.iterator; assert.expect(2); const collection = new Skeletor.Collection([]); assert.strictEqual(collection[$$iterator], collection.values); diff --git a/tsconfig.json b/tsconfig.json index ccfefdb8..3e3e15af 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,8 +9,13 @@ "allowJs": true, "checkJs": true, + // Generate d.ts files + "declaration": true, + "emitDeclarationOnly": true, + "declarationMap": true, + "rootDir": "./src", - "outDir": "./types/", + "outDir": "./src/types/", "baseUrl": "./src/", "esModuleInterop": true,