@converse/skeletor 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +38 -25
  2. package/dist/skeletor.js +9232 -5096
  3. package/dist/skeletor.js.map +1 -1
  4. package/dist/skeletor.min.js +74 -3
  5. package/dist/skeletor.min.js.map +1 -1
  6. package/package.json +21 -11
  7. package/src/collection.js +540 -395
  8. package/src/drivers/sessionStorage.js +222 -219
  9. package/src/element.js +132 -82
  10. package/src/eventemitter.js +187 -0
  11. package/src/helpers.js +143 -118
  12. package/src/{main.js → index.js} +21 -17
  13. package/src/listening.js +81 -0
  14. package/src/model.js +340 -238
  15. package/src/storage.js +222 -230
  16. package/src/types/collection.d.ts +366 -0
  17. package/src/types/collection.d.ts.map +1 -0
  18. package/src/types/drivers/sessionStorage.d.ts +37 -0
  19. package/src/types/drivers/sessionStorage.d.ts.map +1 -0
  20. package/src/types/element.d.ts +115 -0
  21. package/src/types/element.d.ts.map +1 -0
  22. package/src/types/eventemitter.d.ts +93 -0
  23. package/src/types/eventemitter.d.ts.map +1 -0
  24. package/src/types/helpers.d.ts +62 -0
  25. package/src/types/helpers.d.ts.map +1 -0
  26. package/src/types/index.d.ts +19 -0
  27. package/src/types/index.d.ts.map +1 -0
  28. package/src/types/listening.d.ts +40 -0
  29. package/src/types/listening.d.ts.map +1 -0
  30. package/src/types/model.d.ts +230 -0
  31. package/src/types/model.d.ts.map +1 -0
  32. package/src/types/storage.d.ts +35 -0
  33. package/src/types/storage.d.ts.map +1 -0
  34. package/src/types/utils/events.d.ts +28 -0
  35. package/src/types/utils/events.d.ts.map +1 -0
  36. package/src/utils/events.js +174 -0
  37. package/src/drivers/sessionStorage.js~ +0 -307
  38. package/src/events.js +0 -328
  39. package/src/history.js +0 -301
  40. package/src/overview.js +0 -191
  41. package/src/router.js +0 -119
  42. package/src/view.js +0 -267
package/src/element.js CHANGED
@@ -1,45 +1,62 @@
1
- import extend from "lodash-es/extend.js";
2
- import isElement from "lodash-es/isElement.js";
3
- import isFunction from "lodash-es/isFunction.js";
4
- import pick from "lodash-es/pick.js";
5
- import result from "lodash-es/result.js";
6
- import uniqueId from "lodash-es/uniqueId.js";
7
- import { Events } from './events.js';
8
- import { inherits, NotImplementedError } from './helpers.js';
9
- import { render } from 'lit-html';
10
-
11
1
 
12
- const paddedLt = /^\s*</;
13
-
14
- // Caches a local reference to `Element.prototype` for faster access.
15
- const ElementProto = (typeof Element !== 'undefined' && Element.prototype) || {};
2
+ /**
3
+ * @typedef {import('lit-html').TemplateResult} TemplateResult
4
+ */
5
+ import uniqueId from 'lodash-es/uniqueId.js';
6
+ import { render } from 'lit-html';
7
+ import EventEmitter from './eventemitter.js';
16
8
 
17
9
  // Cached regex to split keys for `delegate`.
18
10
  const delegateEventSplitter = /^(\S+)\s*(.*)$/;
19
11
 
20
- // List of view options to be set as properties.
21
- const viewOptions = ['model', 'collection', 'events'];
12
+ class ElementView extends EventEmitter(HTMLElement) {
13
+
14
+ /**
15
+ * @typedef {import('./model.js').Model} Model
16
+ * @typedef {import('./collection.js').Collection} Collection
17
+ * @typedef {Record.<string, any>} Options
18
+ *
19
+ * @callback EventCallback
20
+ * @param {any} event
21
+ * @param {Model} model
22
+ * @param {Collection} collection
23
+ * @param {Options} [options]
24
+ */
25
+
26
+ set events (events) {
27
+ this._declarativeEvents = events;
28
+ }
22
29
 
30
+ get events() {
31
+ return this._declarativeEvents;
32
+ }
23
33
 
24
- export class ElementView extends HTMLElement {
34
+ /**
35
+ * @param {Options} options
36
+ */
37
+ constructor(options={}) {
38
+ super();
25
39
 
26
- events = {}
40
+ // Will be assigned to from Events
41
+ this.stopListening = null;
27
42
 
28
- constructor(options) {
29
- super();
30
43
  // Creating a View creates its initial element outside of the DOM,
31
44
  // if an existing element is not provided...
32
45
  this.cid = uniqueId('view');
46
+ this._declarativeEvents = {};
33
47
  this._domEvents = [];
34
- extend(this, pick(options, viewOptions));
48
+
49
+ const { model, collection, events } = options;
50
+
51
+ Object.assign(this, { model: model, collection, events });
35
52
  }
36
53
 
37
- createRenderRoot () {
54
+ createRenderRoot() {
38
55
  // Render without the shadow DOM
39
56
  return this;
40
57
  }
41
58
 
42
- connectedCallback () {
59
+ connectedCallback() {
43
60
  if (!this._initialized) {
44
61
  this.preinitialize.apply(this, arguments);
45
62
  this.initialize.apply(this, arguments);
@@ -48,43 +65,61 @@ export class ElementView extends HTMLElement {
48
65
  this.delegateEvents();
49
66
  }
50
67
 
51
- disconnectedCallback () {
68
+ disconnectedCallback() {
52
69
  this.undelegateEvents();
53
- this.stopListening();
54
- }
55
-
56
- // preinitialize is an empty function by default. You can override it with a function
57
- // or object. preinitialize will run before any instantiation logic is run in the View
58
- preinitialize () { // eslint-disable-line class-methods-use-this
70
+ this.stopListening?.();
59
71
  }
60
72
 
61
- // Initialize is an empty function by default. Override it with your own
62
- // initialization logic.
63
- initialize() {} // eslint-disable-line class-methods-use-this
64
-
65
- // **render** is the core function that your view should override, in order
66
- // to populate its element (`this.el`), with the appropriate HTML. The
67
- // convention is for **render** to always return `this`.
73
+ /**
74
+ * preinitialize is an empty function by default. You can override it with a function
75
+ * or object. preinitialize will run before any instantiation logic is run in the View
76
+ * eslint-disable-next-line class-methods-use-this
77
+ */
78
+ preinitialize() {}
79
+
80
+ /**
81
+ * Initialize is an empty function by default. Override it with your own
82
+ * initialization logic.
83
+ */
84
+ initialize() {}
85
+
86
+ beforeRender() {}
87
+ afterRender() {}
88
+
89
+ /**
90
+ * **render** is the core function that your view should override, in order
91
+ * to populate its element (`this.el`), with the appropriate HTML. The
92
+ * convention is for **render** to always return `this`.
93
+ */
68
94
  render() {
69
- isFunction(this.beforeRender) && this.beforeRender();
70
- isFunction(this.toHTML) && render(this.toHTML(), this);
71
- isFunction(this.afterRender) && this.afterRender();
95
+ this.beforeRender();
96
+ render(this.toHTML(), this);
97
+ this.afterRender();
72
98
  return this;
73
99
  }
74
100
 
75
- // Set callbacks, where `this.events` is a hash of
76
- //
77
- // *{"event selector": "callback"}*
78
- //
79
- // {
80
- // 'mousedown .title': 'edit',
81
- // 'click .button': 'save',
82
- // 'click .open': function(e) { ... }
83
- // }
84
- //
85
- // pairs. Callbacks will be bound to the view, with `this` set properly.
86
- // Uses event delegation for efficiency.
87
- // Omitting the selector binds the event to `this.el`.
101
+ /**
102
+ * @returns {string|TemplateResult}
103
+ */
104
+ toHTML() {
105
+ return '';
106
+ }
107
+
108
+ /**
109
+ * Set callbacks, where `this.events` is a hash of
110
+ *
111
+ * *{"event selector": "callback"}*
112
+ *
113
+ * {
114
+ * 'mousedown .title': 'edit',
115
+ * 'click .button': 'save',
116
+ * 'click .open': function(e) { ... }
117
+ * }
118
+ *
119
+ * pairs. Callbacks will be bound to the view, with `this` set properly.
120
+ * Uses event delegation for efficiency.
121
+ * Omitting the selector binds the event to `this.el`.
122
+ */
88
123
  delegateEvents() {
89
124
  if (!this.events) {
90
125
  return this;
@@ -92,7 +127,7 @@ export class ElementView extends HTMLElement {
92
127
  this.undelegateEvents();
93
128
  for (const key in this.events) {
94
129
  let method = this.events[key];
95
- if (!isFunction(method)) method = this[method];
130
+ if (typeof method !== 'function') method = this[method];
96
131
  if (!method) continue;
97
132
  const match = key.match(delegateEventSplitter);
98
133
  this.delegate(match[1], match[2], method.bind(this));
@@ -100,15 +135,21 @@ export class ElementView extends HTMLElement {
100
135
  return this;
101
136
  }
102
137
 
103
- // Make a event delegation handler for the given `eventName` and `selector`
104
- // and attach it to `this.el`.
105
- // If selector is empty, the listener will be bound to `this.el`. If not, a
106
- // new handler that will recursively traverse up the event target's DOM
107
- // hierarchy looking for a node that matches the selector. If one is found,
108
- // the event's `delegateTarget` property is set to it and the return the
109
- // result of calling bound `listener` with the parameters given to the
110
- // handler.
138
+ /**
139
+ * Make a event delegation handler for the given `eventName` and `selector`
140
+ * and attach it to `this.el`.
141
+ * If selector is empty, the listener will be bound to `this.el`. If not, a
142
+ * new handler that will recursively traverse up the event target's DOM
143
+ * hierarchy looking for a node that matches the selector. If one is found,
144
+ * the event's `delegateTarget` property is set to it and the return the
145
+ * result of calling bound `listener` with the parameters given to the
146
+ * handler.
147
+ * @param {string} eventName
148
+ * @param {string} selector
149
+ * @param {(ev: Event) => any} listener
150
+ */
111
151
  delegate(eventName, selector, listener) {
152
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
112
153
  const root = this;
113
154
  if (!root) {
114
155
  return this;
@@ -123,29 +164,33 @@ export class ElementView extends HTMLElement {
123
164
  for (let i = 0, len = els.length; i < len; i++) {
124
165
  const item = els[i];
125
166
  item.addEventListener(eventName, listener, false);
126
- this._domEvents.push({el: item, eventName: eventName, handler: listener});
167
+ this._domEvents.push({ el: item, eventName: eventName, handler: listener });
127
168
  }
128
169
  return listener;
129
170
  }
130
171
 
131
- const handler = selector ? function (e) {
132
- let node = e.target || e.srcElement;
133
- for (; node && node != root; node = node.parentNode) {
134
- if (node.matches(selector)) {
135
- e.delegateTarget = node;
136
- listener(e);
172
+ const handler = selector
173
+ ? function (e) {
174
+ let node = e.target || e.srcElement;
175
+ for (; node && node != root; node = node.parentNode) {
176
+ if (node.matches(selector)) {
177
+ e.delegateTarget = node;
178
+ listener(e);
179
+ }
180
+ }
137
181
  }
138
- }
139
- } : listener;
182
+ : listener;
140
183
 
141
184
  this.addEventListener(eventName, handler, false);
142
- this._domEvents.push({el: this, eventName: eventName, handler: handler, listener: listener, selector: selector});
185
+ this._domEvents.push({ el: this, eventName: eventName, handler: handler, listener: listener, selector: selector });
143
186
  return this;
144
187
  }
145
188
 
146
- // Clears all callbacks previously bound to the view by `delegateEvents`.
147
- // You usually don't need to use this, but may wish to if you have multiple
148
- // Backbone views attached to the same DOM element.
189
+ /**
190
+ * Clears all callbacks previously bound to the view by `delegateEvents`.
191
+ * You usually don't need to use this, but may wish to if you have multiple
192
+ * Backbone views attached to the same DOM element.
193
+ */
149
194
  undelegateEvents() {
150
195
  if (this) {
151
196
  for (let i = 0, len = this._domEvents.length; i < len; i++) {
@@ -157,8 +202,13 @@ export class ElementView extends HTMLElement {
157
202
  return this;
158
203
  }
159
204
 
160
- // A finer-grained `undelegateEvents` for removing a single delegated event.
161
- // `selector` and `listener` are both optional.
205
+ /**
206
+ * A finer-grained `undelegateEvents` for removing a single delegated event.
207
+ * `selector` and `listener` are both optional.
208
+ * @param {string} eventName
209
+ * @param {string} selector
210
+ * @param {(ev: Event) => any} listener
211
+ */
162
212
  undelegate(eventName, selector, listener) {
163
213
  if (typeof selector === 'function') {
164
214
  listener = selector;
@@ -169,9 +219,10 @@ export class ElementView extends HTMLElement {
169
219
  let i = handlers.length;
170
220
  while (i--) {
171
221
  const item = handlers[i];
172
- const match = item.eventName === eventName &&
173
- (listener ? item.listener === listener : true) &&
174
- (selector ? item.selector === selector : true);
222
+ const match =
223
+ item.eventName === eventName &&
224
+ (listener ? item.listener === listener : true) &&
225
+ (selector ? item.selector === selector : true);
175
226
 
176
227
  if (!match) {
177
228
  continue;
@@ -184,5 +235,4 @@ export class ElementView extends HTMLElement {
184
235
  }
185
236
  }
186
237
 
187
- // Set up all inheritable **View** properties and methods.
188
- Object.assign(ElementView.prototype, Events);
238
+ export default ElementView;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * @copyright 2010-2019 Jeremy Ashkenas and DocumentCloud
3
+ * @copyright 2023 JC Brand
4
+ */
5
+ import isEmpty from 'lodash-es/isEmpty.js';
6
+ import keys from 'lodash-es/keys.js';
7
+ import uniqueId from 'lodash-es/uniqueId.js';
8
+ import Listening from './listening.js';
9
+ import { eventsApi, onApi, offApi, onceMap, tryCatchOn, triggerApi } from './utils/events.js';
10
+
11
+ // A private global variable to share between listeners and listenees.
12
+ let _listening;
13
+
14
+ /**
15
+ * @function
16
+ * @template {new(...args: any[]) => {}} ClassConstructor
17
+ * @param {ClassConstructor} Base
18
+ */
19
+ export function EventEmitter(Base) {
20
+ return class EventEmitter extends Base {
21
+ /**
22
+ * @typedef {import('./model.js').Model} Model
23
+ * @typedef {import('./collection.js').Collection} Collection
24
+ * @typedef {Record.<string, any>} Options
25
+ *
26
+ * @callback EventCallback
27
+ * @param {any} event
28
+ * @param {Model} model
29
+ * @param {Collection} collection
30
+ * @param {Options} [options]
31
+ */
32
+
33
+ /**
34
+ * Bind an event to a `callback` function. Passing `"all"` will bind
35
+ * the callback to all events fired.
36
+ * @param {string} name
37
+ * @param {EventCallback} callback
38
+ * @param {any} context
39
+ * @return {EventEmitter}
40
+ */
41
+ on(name, callback, context) {
42
+ this._events = eventsApi(onApi, this._events || {}, name, callback, {
43
+ context: context,
44
+ ctx: this,
45
+ listening: _listening,
46
+ });
47
+
48
+ if (_listening) {
49
+ const listeners = this._listeners || (this._listeners = {});
50
+ listeners[_listening.id] = _listening;
51
+ // Allow the listening to use a counter, instead of tracking
52
+ // callbacks for library interop
53
+ _listening.interop = false;
54
+ }
55
+
56
+ return this;
57
+ }
58
+
59
+ /**
60
+ * Inversion-of-control versions of `on`. Tell *this* object to listen to
61
+ * an event in another object... keeping track of what it's listening to
62
+ * for easier unbinding later.
63
+ * @param {any} obj
64
+ * @param {string} name
65
+ * @param {EventCallback} [callback]
66
+ * @return {EventEmitter}
67
+ */
68
+ listenTo(obj, name, callback) {
69
+ if (!obj) return this;
70
+ const id = obj._listenId || (obj._listenId = uniqueId('l'));
71
+ const listeningTo = this._listeningTo || (this._listeningTo = {});
72
+ let listening = (_listening = listeningTo[id]);
73
+
74
+ // This object is not listening to any other events on `obj` yet.
75
+ // Setup the necessary references to track the listening callbacks.
76
+ if (!listening) {
77
+ this._listenId || (this._listenId = uniqueId('l'));
78
+ listening = _listening = listeningTo[id] = new Listening(this, obj);
79
+ }
80
+
81
+ // Bind callbacks on obj.
82
+ const error = tryCatchOn(obj, name, callback, this);
83
+ _listening = undefined;
84
+
85
+ if (error) throw error;
86
+ // If the target obj is not Backbone.Events, track events manually.
87
+ if (listening.interop) listening.start(name, callback, this, _listening);
88
+
89
+ return this;
90
+ }
91
+
92
+ /**
93
+ * Remove one or many callbacks. If `context` is null, removes all
94
+ * callbacks with that function. If `callback` is null, removes all
95
+ * callbacks for the event. If `name` is null, removes all bound
96
+ * callbacks for all events.
97
+ * @param {string} name
98
+ * @param {EventCallback} callback
99
+ * @param {any} [context]
100
+ * @return {EventEmitter}
101
+ */
102
+ off(name, callback, context) {
103
+ if (!this._events) return this;
104
+ this._events = eventsApi(offApi, this._events, name, callback, {
105
+ context: context,
106
+ listeners: this._listeners,
107
+ });
108
+
109
+ return this;
110
+ }
111
+
112
+ /**
113
+ * Tell this object to stop listening to either specific events ... or
114
+ * to every object it's currently listening to.
115
+ * @param {any} [obj]
116
+ * @param {string} [name]
117
+ * @param {EventCallback} [callback]
118
+ * @return {EventEmitter}
119
+ */
120
+ stopListening(obj, name, callback) {
121
+ const listeningTo = this._listeningTo;
122
+ if (!listeningTo) return this;
123
+
124
+ const ids = obj ? [obj._listenId] : keys(listeningTo);
125
+ for (let i = 0; i < ids.length; i++) {
126
+ const listening = listeningTo[ids[i]];
127
+
128
+ // If listening doesn't exist, this object is not currently
129
+ // listening to obj. Break out early.
130
+ if (!listening) break;
131
+
132
+ listening.obj.off(name, callback, this);
133
+ if (listening.interop) listening.stop(name, callback);
134
+ }
135
+ if (isEmpty(listeningTo)) this._listeningTo = undefined;
136
+
137
+ return this;
138
+ }
139
+
140
+ /**
141
+ * Bind an event to only be triggered a single time. After the first time
142
+ * the callback is invoked, its listener will be removed. If multiple events
143
+ * are passed in using the space-separated syntax, the handler will fire
144
+ * once for each event, not once for a combination of all events.
145
+ * @param {string} name
146
+ * @param {EventCallback} callback
147
+ * @param {any} context
148
+ * @return {EventEmitter}
149
+ */
150
+ once(name, callback, context) {
151
+ // Map the event into a `{event: once}` object.
152
+ const events = eventsApi(onceMap, {}, name, callback, this.off.bind(this));
153
+ if (typeof name === 'string' && (context === null || context === undefined)) callback = undefined;
154
+ return this.on(events, callback, context);
155
+ }
156
+
157
+ /**
158
+ * Inversion-of-control versions of `once`.
159
+ * @param {any} obj
160
+ * @param {string} name
161
+ * @param {EventCallback} [callback]
162
+ * @return {EventEmitter}
163
+ */
164
+ listenToOnce(obj, name, callback) {
165
+ // Map the event into a `{event: once}` object.
166
+ const events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj));
167
+ return this.listenTo(obj, events);
168
+ }
169
+
170
+ /**
171
+ * Trigger one or many events, firing all bound callbacks. Callbacks are
172
+ * passed the same arguments as `trigger` is, apart from the event name
173
+ * (unless you're listening on `"all"`, which will cause your callback to
174
+ * receive the true name of the event as the first argument).
175
+ * @param {string} name
176
+ * @return {EventEmitter}
177
+ */
178
+ trigger(name, ...args) {
179
+ if (!this._events) return this;
180
+
181
+ eventsApi(triggerApi, this._events, name, undefined, args);
182
+ return this;
183
+ }
184
+ };
185
+ }
186
+
187
+ export default EventEmitter;