@lumjs/core 1.36.0 → 1.37.1
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.
- package/lib/events/event.js +10 -6
- package/lib/events/listener.js +14 -8
- package/lib/events/registry.js +201 -69
- package/lib/obj/clone.js +3 -0
- package/lib/obj/index.js +2 -1
- package/lib/obj/unlocked.js +87 -0
- package/lib/types/isa.js +1 -1
- package/package.json +1 -1
package/lib/events/event.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const {F,isObj} = require('../types');
|
|
3
|
+
const {SY,F,isObj} = require('../types');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* An Event object to emit to handler callbacks.
|
|
7
7
|
*
|
|
8
8
|
* @prop {module:@lumjs/core/events.Listener} eventListener
|
|
9
9
|
* The event Listener instance this event was emitted from.
|
|
10
|
-
* @prop {string}
|
|
10
|
+
* @prop {(string|Symbol)} type - The event type that was triggered.
|
|
11
|
+
* @prop {string} name - The event name that was triggered;
|
|
12
|
+
* if `type` is a string this will be the same value,
|
|
13
|
+
* for a Symbol type this will be the `type.description` value.
|
|
11
14
|
* @prop {object} target - Target object for this event.
|
|
12
15
|
* @prop {Array} args - Arguments passed to `emit()`
|
|
13
16
|
* @prop {object} options - Composes options from the
|
|
@@ -38,17 +41,18 @@ class LumEvent
|
|
|
38
41
|
* @protected
|
|
39
42
|
* @param {module:@lumjs/core/events.Listener} listener
|
|
40
43
|
* @param {object} target
|
|
41
|
-
* @param {string}
|
|
42
|
-
* @param {Array}
|
|
44
|
+
* @param {(string|Symbol)} type
|
|
45
|
+
* @param {Array} args
|
|
43
46
|
* @param {object} status
|
|
44
47
|
*/
|
|
45
|
-
constructor(listener, target,
|
|
48
|
+
constructor(listener, target, type, args, status)
|
|
46
49
|
{
|
|
47
50
|
const reg = listener.registry;
|
|
48
51
|
this.eventListener = listener;
|
|
49
52
|
this.args = args;
|
|
50
53
|
this.target = target;
|
|
51
|
-
this.
|
|
54
|
+
this.type = type;
|
|
55
|
+
this.name = (typeof type === SY) ? type.description : type;
|
|
52
56
|
this.emitStatus = status;
|
|
53
57
|
this.options = Object.assign({},
|
|
54
58
|
reg.options,
|
package/lib/events/listener.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const {F,isObj} = require('../types');
|
|
4
4
|
const Event = require('./event');
|
|
5
5
|
|
|
6
|
-
const REMOVE_OPTS = ['listener','handler','eventNames'];
|
|
6
|
+
const REMOVE_OPTS = ['listener','handler','eventNames','eventTypes'];
|
|
7
7
|
|
|
8
8
|
function makeOpts(spec)
|
|
9
9
|
{
|
|
@@ -41,7 +41,8 @@ function isListener(v)
|
|
|
41
41
|
* @prop {module:@lumjs/core/events.Registry} registry
|
|
42
42
|
* The Registry instance this Listener belongs to.
|
|
43
43
|
* @prop {(function|object)} handler - Event handler callback
|
|
44
|
-
* @prop {Set}
|
|
44
|
+
* @prop {Set} eventTypes - A set of all event types handled by this
|
|
45
|
+
* @prop {Set} eventNames - Alias to `eventTypes`
|
|
45
46
|
* @prop {object} options - Options specific to this listener.
|
|
46
47
|
*
|
|
47
48
|
* See {@link module:@lumjs/core/events.Registry#makeListener makeListener()}
|
|
@@ -76,7 +77,8 @@ class LumEventListener
|
|
|
76
77
|
// Assign the rest here.
|
|
77
78
|
this.registry = registry;
|
|
78
79
|
this.options = makeOpts(spec);
|
|
79
|
-
|
|
80
|
+
const events = spec.eventTypes ?? spec.eventNames;
|
|
81
|
+
this.eventTypes = this.eventNames = registry.getEventNames(events);
|
|
80
82
|
|
|
81
83
|
const setup = this.options.setupListener ?? registry.options.setupListener;
|
|
82
84
|
if (typeof setup === F)
|
|
@@ -86,12 +88,12 @@ class LumEventListener
|
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
/**
|
|
89
|
-
* See if there is at least one item in `this.
|
|
91
|
+
* See if there is at least one item in `this.eventTypes`
|
|
90
92
|
* @type {boolean}
|
|
91
93
|
*/
|
|
92
94
|
get hasEvents()
|
|
93
95
|
{
|
|
94
|
-
return this.
|
|
96
|
+
return this.eventTypes.size > 0;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
/**
|
|
@@ -100,15 +102,15 @@ class LumEventListener
|
|
|
100
102
|
*
|
|
101
103
|
* This is a *protected method* and should not be called directly.
|
|
102
104
|
* @protected
|
|
103
|
-
* @param {string}
|
|
105
|
+
* @param {string} type - A single event type/name that was triggered
|
|
104
106
|
* @param {object} target - A single target object
|
|
105
107
|
* @param {Array} args - Arguments passed to `emit()`
|
|
106
108
|
* @param {module:@lumjs/core/events~Status} status - Emit status info
|
|
107
109
|
* @returns {module:@lumjs/core/events.Event} The new Event that was emitted
|
|
108
110
|
*/
|
|
109
|
-
emitEvent(
|
|
111
|
+
emitEvent(type, target, args, status)
|
|
110
112
|
{
|
|
111
|
-
const event = new Event(this, target,
|
|
113
|
+
const event = new Event(this, target, type, args, status);
|
|
112
114
|
|
|
113
115
|
if (typeof this.handler === F)
|
|
114
116
|
{ // The simplest is the good old function
|
|
@@ -127,6 +129,10 @@ class LumEventListener
|
|
|
127
129
|
return event;
|
|
128
130
|
}
|
|
129
131
|
|
|
132
|
+
static get classProps()
|
|
133
|
+
{
|
|
134
|
+
return Object.getOwnPropertyNames(this.prototype);
|
|
135
|
+
}
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
LumEventListener.isListener = isListener;
|
package/lib/events/registry.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const {S,F,isObj,def,isIterable} = require('../types');
|
|
3
|
+
const {S,F,SY,isObj,def,isIterable} = require('../types');
|
|
4
4
|
const Listener = require('./listener');
|
|
5
5
|
const RegSym = Symbol('@lumjs/core/events:registry');
|
|
6
|
+
const cp = Object.assign;
|
|
6
7
|
|
|
7
8
|
const DEF_EXTENDS =
|
|
8
9
|
{
|
|
@@ -13,6 +14,13 @@ const DEF_EXTENDS =
|
|
|
13
14
|
once: null,
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
const INT_EXTENDS =
|
|
18
|
+
{
|
|
19
|
+
listeners: true,
|
|
20
|
+
results: true,
|
|
21
|
+
onDemand: false,
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
const DEF_OPTIONS =
|
|
17
25
|
{
|
|
18
26
|
delimiter: /\s+/,
|
|
@@ -20,6 +28,12 @@ const DEF_OPTIONS =
|
|
|
20
28
|
wildcard: '*',
|
|
21
29
|
}
|
|
22
30
|
|
|
31
|
+
const RES_PROPS =
|
|
32
|
+
[
|
|
33
|
+
'eventNames', 'eventTypes', 'targets', 'multiMatch', 'onceRemoved',
|
|
34
|
+
'stopEmitting', 'emitted', 'targetListeners', 'registry',
|
|
35
|
+
]
|
|
36
|
+
|
|
23
37
|
/**
|
|
24
38
|
* Has a target object been registered with an event registry?
|
|
25
39
|
* @param {object} target
|
|
@@ -33,7 +47,7 @@ const isRegistered = target => isObj(target[RegSym]);
|
|
|
33
47
|
* @private
|
|
34
48
|
* @param {object} target - Object to get metadata for
|
|
35
49
|
* @param {boolean} [create=false] Create metadata if it's not found?
|
|
36
|
-
* @returns {object} metadata (TODO: schema docs)
|
|
50
|
+
* @returns {(object|undefined)} metadata (TODO: schema docs)
|
|
37
51
|
* @alias module:@lumjs/core/events.Registry.getMetadata
|
|
38
52
|
*/
|
|
39
53
|
function getMetadata(target, create=false)
|
|
@@ -44,10 +58,16 @@ function getMetadata(target, create=false)
|
|
|
44
58
|
}
|
|
45
59
|
else if (create)
|
|
46
60
|
{ // Create new metadata
|
|
61
|
+
const exts = Object.keys(DEF_EXTENDS);
|
|
47
62
|
const tpm =
|
|
48
63
|
{
|
|
49
64
|
r: new Map(),
|
|
50
65
|
p: {},
|
|
66
|
+
x: {},
|
|
67
|
+
}
|
|
68
|
+
for (const ext of exts)
|
|
69
|
+
{
|
|
70
|
+
tpm.x[ext] = [];
|
|
51
71
|
}
|
|
52
72
|
def(target, RegSym, tpm);
|
|
53
73
|
return tpm;
|
|
@@ -98,29 +118,53 @@ class LumEventRegistry
|
|
|
98
118
|
* If you want to use a `function` as an actual target, you'll need to
|
|
99
119
|
* wrap it in an array or other iterable object.
|
|
100
120
|
*
|
|
101
|
-
* @param {object} [opts] Options
|
|
121
|
+
* @param {object} [opts] Options
|
|
122
|
+
*
|
|
123
|
+
* A _compiled_ version is saved to the `options` property.
|
|
124
|
+
* The compiled version includes a bunch of defaults, and various
|
|
125
|
+
* compose rules (mostly for the `.extend` nested options).
|
|
102
126
|
*
|
|
103
127
|
* @param {(RegExp|string)} [opts.delimiter=/\s+/] Used to split event names
|
|
104
128
|
*
|
|
105
|
-
* @param {
|
|
106
|
-
*
|
|
107
|
-
*
|
|
129
|
+
* @param {object} [opts.extend] Options for wrapper methods/properties
|
|
130
|
+
*
|
|
131
|
+
* The `boolean` options determine if extension methods will be added to
|
|
132
|
+
* certain types of objects (and in some cases, when to do so).
|
|
108
133
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
134
|
+
* The `?string` options are each the names of properties/methods in the
|
|
135
|
+
* Registry class. If they are set to a `string` then that will be the
|
|
136
|
+
* name used for the wrapper property/method added to objects. If it
|
|
137
|
+
* is explicitly set to `null` it means skip adding a wrapper for that
|
|
138
|
+
* method/property. If it is omitted entirely the default will be used.
|
|
111
139
|
*
|
|
112
|
-
*
|
|
113
|
-
* it disables adding extension properties entirely.
|
|
140
|
+
* @param {boolean} [opts.extend.targets] Extend target objects?
|
|
114
141
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
142
|
+
* The default will be `true` when `targets` is an `object`, or `false`
|
|
143
|
+
* when `targets` is a `function`.
|
|
144
|
+
*
|
|
145
|
+
* @param {boolean} [opts.extend.listeners=true] Extend Listener instances?
|
|
146
|
+
* As returned by `makeListener()`, `listen()`, and `once()`
|
|
147
|
+
* @param {boolean} [opts.extend.results=true] Extend `emit()` results?
|
|
148
|
+
* @param {boolean} [opts.extend.onDemand=false] On-demand target setup
|
|
149
|
+
*
|
|
150
|
+
* If `targets` was a `function` and this is set to `true`, then
|
|
151
|
+
* we'll perform the target setup on every emit() call. The setup process
|
|
152
|
+
* is skipped on any targets that have already been set up, so this
|
|
153
|
+
* is meant for dynamic targets that may change on every call.
|
|
154
|
+
*
|
|
155
|
+
* @param {?string} [opts.extend.registry="events"] Registry property;
|
|
156
|
+
* only added to `targets`, never to listeners or results which have
|
|
157
|
+
* their own inherent `registry` property already.
|
|
117
158
|
*
|
|
118
|
-
* @param {?string} [opts.extend.registry="events"] Registry property
|
|
119
159
|
* @param {?string} [opts.extend.emit="emit"] `emit()` proxy method
|
|
120
160
|
* @param {?string} [opts.extend.listen="on"] `listen()` proxy method
|
|
121
161
|
* @param {?string} [opts.extend.once=null] `once()` proxy method
|
|
122
162
|
* @param {?string} [opts.extend.remove=null] `remove()` proxy method
|
|
123
163
|
*
|
|
164
|
+
* The `remove` wrapper method added to Listener instances is slightly
|
|
165
|
+
* different than the one added to other objects, as if you call it
|
|
166
|
+
* with no arguments, it will pass the Listener itself as the argument.
|
|
167
|
+
*
|
|
124
168
|
* @param {boolean} [opts.multiMatch=false]
|
|
125
169
|
* If a registered listener has multiple event names, and a call
|
|
126
170
|
* to `emit()` also has multiple event names, the value of this
|
|
@@ -159,7 +203,7 @@ class LumEventRegistry
|
|
|
159
203
|
*/
|
|
160
204
|
constructor(targets, opts={})
|
|
161
205
|
{
|
|
162
|
-
let defExt; // Default opts.extend value
|
|
206
|
+
let defExt; // Default opts.extend.targets value
|
|
163
207
|
if (typeof targets === F)
|
|
164
208
|
{ // A dynamic getter method
|
|
165
209
|
this.funTargets = true;
|
|
@@ -181,47 +225,54 @@ class LumEventRegistry
|
|
|
181
225
|
defExt = true;
|
|
182
226
|
}
|
|
183
227
|
|
|
184
|
-
|
|
228
|
+
// Build composite extend rules
|
|
229
|
+
const extend = cp(
|
|
230
|
+
{targets: defExt},
|
|
231
|
+
INT_EXTENDS,
|
|
232
|
+
DEF_EXTENDS,
|
|
233
|
+
opts.extend);
|
|
234
|
+
|
|
235
|
+
// Now compile the final options
|
|
236
|
+
this.options = cp({}, DEF_OPTIONS, opts, {extend});
|
|
185
237
|
|
|
186
238
|
this.allListeners = new Set();
|
|
187
239
|
this.listenersFor = new Map();
|
|
188
240
|
|
|
189
|
-
this.
|
|
241
|
+
this.setupTargets(targets);
|
|
190
242
|
} // constructor()
|
|
191
243
|
|
|
192
244
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
245
|
+
* Set up target objects
|
|
246
|
+
*
|
|
247
|
+
* Always sets the necessary metadata on each target.
|
|
248
|
+
* May also extend the targets with wrapper properties and methods
|
|
249
|
+
* depending on the `options.extend` values set.
|
|
250
|
+
*
|
|
251
|
+
* Not meant to be called from outside code.
|
|
196
252
|
* @private
|
|
197
253
|
* @param {Iterable} targets - Targets to extend
|
|
198
254
|
* @returns {module:@lumjs/core/events.Registry} `this`
|
|
199
255
|
*/
|
|
200
|
-
|
|
256
|
+
setupTargets(targets)
|
|
201
257
|
{
|
|
202
258
|
const opts = this.options;
|
|
203
259
|
const extOpts = opts.extend;
|
|
204
|
-
|
|
205
|
-
let intNames = null, extNames = null;
|
|
206
|
-
|
|
207
|
-
if (extOpts)
|
|
208
|
-
{
|
|
209
|
-
intNames = Object.keys(DEF_EXTENDS);
|
|
210
|
-
extNames = Object.assign({}, DEF_EXTENDS, extOpts);
|
|
211
|
-
}
|
|
260
|
+
const intNames = extOpts.targets ? Object.keys(DEF_EXTENDS) : null;
|
|
212
261
|
|
|
213
262
|
for (const target of targets)
|
|
214
263
|
{
|
|
215
264
|
const tps = {}, tpm = getMetadata(target, true);
|
|
265
|
+
if (tpm.r.has(this)) continue; // Already set up with this registry.
|
|
216
266
|
tpm.r.set(this, tps);
|
|
217
267
|
|
|
218
|
-
if (extOpts)
|
|
268
|
+
if (extOpts.targets)
|
|
219
269
|
{
|
|
220
270
|
for (const iname of intNames)
|
|
221
271
|
{
|
|
222
|
-
if (typeof
|
|
272
|
+
if ((typeof extOpts[iname] === S && extOpts[iname].trim() !== '')
|
|
273
|
+
|| typeof extOpts[iname] === SY)
|
|
223
274
|
{
|
|
224
|
-
const ename =
|
|
275
|
+
const ename = extOpts[iname];
|
|
225
276
|
const value = iname === 'registry'
|
|
226
277
|
? this // The registry instance itself
|
|
227
278
|
: (...args) => this[iname](...args) // A proxy method
|
|
@@ -230,6 +281,7 @@ class LumEventRegistry
|
|
|
230
281
|
def(target, ename, {value});
|
|
231
282
|
tps[ename] = iname;
|
|
232
283
|
tpm.p[ename] = this;
|
|
284
|
+
tpm.x[iname].push([this, ename]);
|
|
233
285
|
}
|
|
234
286
|
else
|
|
235
287
|
{
|
|
@@ -240,34 +292,79 @@ class LumEventRegistry
|
|
|
240
292
|
}
|
|
241
293
|
}
|
|
242
294
|
}
|
|
295
|
+
|
|
296
|
+
return this;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Add extension methods to internal objects;
|
|
301
|
+
* currently supports Listener instances and result/status objects.
|
|
302
|
+
* Not meant to be called from outside code.
|
|
303
|
+
* @private
|
|
304
|
+
* @param {object} obj - Internal object to extend
|
|
305
|
+
* @returns {module:@lumjs/core/events.Registry} `this`
|
|
306
|
+
*/
|
|
307
|
+
extendInternal(obj)
|
|
308
|
+
{
|
|
309
|
+
const opts = this.options;
|
|
310
|
+
const extOpts = opts.extend;
|
|
311
|
+
const intNames = Object.keys(DEF_EXTENDS);
|
|
312
|
+
const isLs = (obj instanceof Listener);
|
|
313
|
+
const reserved = isLs ? Listener.classProps : RES_PROPS;
|
|
314
|
+
|
|
315
|
+
for (const iname of intNames)
|
|
316
|
+
{
|
|
317
|
+
if (iname === 'registry') continue; // skip the registry property
|
|
318
|
+
if ((typeof extOpts[iname] === S && extOpts[iname].trim() !== '')
|
|
319
|
+
|| typeof extOpts[iname] === SY)
|
|
320
|
+
{
|
|
321
|
+
const ename = extOpts[iname];
|
|
322
|
+
if (reserved.includes(ename))
|
|
323
|
+
{ // Skip reserved names
|
|
324
|
+
console.warn("reserved property", {ename, obj, registry: this});
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const value = (isLs && iname === 'remove')
|
|
329
|
+
? (what=obj) => this.remove(what) // special remove wrapper
|
|
330
|
+
: (...args) => this[iname](...args) // regular wrapper method
|
|
331
|
+
if (opts.overwrite || obj[ename] === undefined)
|
|
332
|
+
{
|
|
333
|
+
def(obj, ename, {value});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
243
338
|
return this;
|
|
244
339
|
}
|
|
245
340
|
|
|
246
341
|
/**
|
|
247
342
|
* Build a new Listener instance; used by `listen()` method.
|
|
248
343
|
*
|
|
249
|
-
* @param {(string|object)}
|
|
344
|
+
* @param {(string|symbol|object)} eventTypes
|
|
250
345
|
* What this does depends on the type, and the number of arguments passed.
|
|
251
346
|
*
|
|
252
347
|
* If this is an `object` **AND** is *the only argument* passed,
|
|
253
|
-
* it will be used as the `spec`, and the `spec.
|
|
348
|
+
* it will be used as the `spec`, and the `spec.eventTypes`
|
|
254
349
|
* and `spec.listener` properties will become mandatory.
|
|
255
350
|
*
|
|
256
|
-
* If it's
|
|
257
|
-
* will be used as the `spec.
|
|
351
|
+
* If it's NOT an object *OR* there is more than one argument, this
|
|
352
|
+
* will be used as the `spec.eventTypes` property.
|
|
258
353
|
*
|
|
259
354
|
* @param {module:@lumjs/core/events~Handler} [handler]
|
|
260
355
|
* Used as the `spec.handler` property if specified.
|
|
261
356
|
*
|
|
262
|
-
* This is mandatory if `
|
|
357
|
+
* This is mandatory if `eventTypes` argument is a string or Symbol!
|
|
263
358
|
*
|
|
264
359
|
* @param {object} [spec] The listener specification rules
|
|
265
360
|
*
|
|
266
|
-
* @param {(string|Iterable)} [spec.
|
|
361
|
+
* @param {(string|symbol|Iterable)} [spec.eventTypes] Event type(s)
|
|
267
362
|
*
|
|
268
363
|
* See {@link module:@lumjs/core/events.Registry#getEventNames} for details.
|
|
269
364
|
*
|
|
270
|
-
* @param {
|
|
365
|
+
* @param {(string|symbol|Iterable)} [spec.eventNames] Alias of `eventTypes`
|
|
366
|
+
*
|
|
367
|
+
* @param {module:@lumjs/core/events~Handler} [spec.handler] Event handler
|
|
271
368
|
*
|
|
272
369
|
* @param {(function|object)} [spec.listener] An alias for `handler`
|
|
273
370
|
*
|
|
@@ -279,8 +376,8 @@ class LumEventRegistry
|
|
|
279
376
|
*
|
|
280
377
|
* If `spec.options` is used, the properties in it take precedence over
|
|
281
378
|
* those directly in the `spec` object. Note that you cannot use the
|
|
282
|
-
* names `listener`, `handler` or `eventNames` as option
|
|
283
|
-
* and if found, they will be removed.
|
|
379
|
+
* names `listener`, `handler`, `eventTypes`, or `eventNames` as option
|
|
380
|
+
* properties, and if found, they will be removed.
|
|
284
381
|
*
|
|
285
382
|
* You may also override the `setupEvent` and `setupListener` registry
|
|
286
383
|
* options here if needed.
|
|
@@ -305,16 +402,21 @@ class LumEventRegistry
|
|
|
305
402
|
}
|
|
306
403
|
else if (args.length === 1 && isObj(args[0]))
|
|
307
404
|
{ // listen(spec)
|
|
308
|
-
spec =
|
|
405
|
+
spec = cp({}, args[0]);
|
|
309
406
|
}
|
|
310
407
|
else
|
|
311
|
-
{ // listen(
|
|
312
|
-
spec =
|
|
313
|
-
spec.
|
|
408
|
+
{ // listen(eventTypes, listener, [spec])
|
|
409
|
+
spec = cp({}, args[2]);
|
|
410
|
+
spec.eventTypes = args[0];
|
|
314
411
|
spec.handler = args[1];
|
|
315
412
|
}
|
|
316
413
|
|
|
317
|
-
|
|
414
|
+
const lsnr = new Listener(this, spec);
|
|
415
|
+
if (this.options.extend.listeners)
|
|
416
|
+
{
|
|
417
|
+
this.extendInternal(lsnr);
|
|
418
|
+
}
|
|
419
|
+
return lsnr;
|
|
318
420
|
}
|
|
319
421
|
|
|
320
422
|
/**
|
|
@@ -373,7 +475,7 @@ class LumEventRegistry
|
|
|
373
475
|
|
|
374
476
|
this.allListeners.add(listener);
|
|
375
477
|
|
|
376
|
-
for (const ename of listener.
|
|
478
|
+
for (const ename of listener.eventTypes)
|
|
377
479
|
{
|
|
378
480
|
let lset;
|
|
379
481
|
if (this.listenersFor.has(ename))
|
|
@@ -431,7 +533,7 @@ class LumEventRegistry
|
|
|
431
533
|
const eventListeners = this.listenersFor.get(name);
|
|
432
534
|
for (const lsnr of eventListeners)
|
|
433
535
|
{
|
|
434
|
-
lsnr.
|
|
536
|
+
lsnr.eventTypes.delete(name);
|
|
435
537
|
if (!lsnr.hasEvents)
|
|
436
538
|
{ // The last event name was removed.
|
|
437
539
|
this.removeListeners(lsnr);
|
|
@@ -456,7 +558,7 @@ class LumEventRegistry
|
|
|
456
558
|
{ // First remove it from allListeners
|
|
457
559
|
this.allListeners.delete(listener);
|
|
458
560
|
|
|
459
|
-
for (const ename of listener.
|
|
561
|
+
for (const ename of listener.eventTypes)
|
|
460
562
|
{
|
|
461
563
|
if (this.listenersFor.has(ename))
|
|
462
564
|
{
|
|
@@ -477,6 +579,7 @@ class LumEventRegistry
|
|
|
477
579
|
* - If this is the `wildcard` string, then this will call `removeAll()`.
|
|
478
580
|
* - If this is any other `string` it will be split using `splitNames()`,
|
|
479
581
|
* and the resulting strings passed as arguments to `removeEvents()`.
|
|
582
|
+
* - If this is a `Symbol` it will be passed to `removeEvents()`.
|
|
480
583
|
* - If this is a `Listener` instance, its passed to `removeListeners()`.
|
|
481
584
|
*
|
|
482
585
|
* @returns {module:@lumjs/core/events.Registry} `this`
|
|
@@ -493,6 +596,10 @@ class LumEventRegistry
|
|
|
493
596
|
const events = this.splitNames(what);
|
|
494
597
|
return this.removeEvents(...events);
|
|
495
598
|
}
|
|
599
|
+
else if (typeof what === SY)
|
|
600
|
+
{
|
|
601
|
+
return this.removeEvents(what);
|
|
602
|
+
}
|
|
496
603
|
else if (what instanceof Listener)
|
|
497
604
|
{
|
|
498
605
|
return this.removeListeners(what);
|
|
@@ -507,9 +614,8 @@ class LumEventRegistry
|
|
|
507
614
|
/**
|
|
508
615
|
* Emit (trigger) one or more events.
|
|
509
616
|
*
|
|
510
|
-
* @param {(string|
|
|
511
|
-
*
|
|
512
|
-
* If this is a single `string` it will be split via `splitNames()`.
|
|
617
|
+
* @param {(string|symbol|Array)} eventTypes - Events to emit;
|
|
618
|
+
* see {@link module:@lumjs/core/events#getEventTypes} for details.
|
|
513
619
|
*
|
|
514
620
|
* @param {object} [data] A data object (highly recommended);
|
|
515
621
|
* will be assigned to `event.data` if specified.
|
|
@@ -522,20 +628,32 @@ class LumEventRegistry
|
|
|
522
628
|
*
|
|
523
629
|
* @returns {module:@lumjs/core/events~Status}
|
|
524
630
|
*/
|
|
525
|
-
emit(
|
|
631
|
+
emit(eventTypes, ...args)
|
|
526
632
|
{
|
|
633
|
+
const extOpts = this.options.extend;
|
|
527
634
|
const sti =
|
|
528
635
|
{
|
|
529
|
-
|
|
636
|
+
eventTypes: this.getEventTypes(eventTypes),
|
|
530
637
|
multiMatch: this.options.multiMatch,
|
|
531
638
|
onceRemoved: new Set(),
|
|
532
639
|
stopEmitting: false,
|
|
533
640
|
emitted: [],
|
|
641
|
+
registry: this,
|
|
642
|
+
}
|
|
643
|
+
sti.eventNames = sti.eventTypes;
|
|
644
|
+
|
|
645
|
+
if (extOpts.results)
|
|
646
|
+
{
|
|
647
|
+
this.extendInternal(sti);
|
|
534
648
|
}
|
|
535
649
|
|
|
536
650
|
{ // Get the targets.
|
|
537
651
|
const tgs = this.getTargets(sti);
|
|
538
652
|
sti.targets = (tgs instanceof Set) ? tgs : new Set(tgs);
|
|
653
|
+
if (this.funTargets && extOpts.onDemand)
|
|
654
|
+
{
|
|
655
|
+
this.setupTargets(sti.targets);
|
|
656
|
+
}
|
|
539
657
|
}
|
|
540
658
|
|
|
541
659
|
const wilds = this.listenersFor.get(this.options.wildcard);
|
|
@@ -543,7 +661,7 @@ class LumEventRegistry
|
|
|
543
661
|
emitting: for (const tg of sti.targets)
|
|
544
662
|
{
|
|
545
663
|
const called = sti.targetListeners = new Set();
|
|
546
|
-
for (const ename of sti.
|
|
664
|
+
for (const ename of sti.eventTypes)
|
|
547
665
|
{
|
|
548
666
|
if (!this.listenersFor.has(ename)) continue;
|
|
549
667
|
|
|
@@ -579,8 +697,6 @@ class LumEventRegistry
|
|
|
579
697
|
return sti;
|
|
580
698
|
}
|
|
581
699
|
|
|
582
|
-
|
|
583
|
-
|
|
584
700
|
/**
|
|
585
701
|
* Register additional target objects
|
|
586
702
|
* @param {...object} addTargets - Target objects to register
|
|
@@ -617,7 +733,7 @@ class LumEventRegistry
|
|
|
617
733
|
}
|
|
618
734
|
}
|
|
619
735
|
|
|
620
|
-
this.
|
|
736
|
+
this.setupTargets(addTargets);
|
|
621
737
|
return this;
|
|
622
738
|
}
|
|
623
739
|
|
|
@@ -693,32 +809,37 @@ class LumEventRegistry
|
|
|
693
809
|
}
|
|
694
810
|
|
|
695
811
|
/**
|
|
696
|
-
* Get a Set of event names from various kinds of values
|
|
697
|
-
* @param {(string|Iterable)}
|
|
812
|
+
* Get a Set of event types/names from various kinds of values
|
|
813
|
+
* @param {(string|symbol|Iterable)} types - Event types source
|
|
698
814
|
*
|
|
699
815
|
* If this is a string, it'll be passed to `splitNames()`.
|
|
700
|
-
* If it's
|
|
816
|
+
* If it's a Symbol, it'll be wrapped in a Set.
|
|
817
|
+
* If it's any kind of Iterable value, it'll be converted to a Set.
|
|
701
818
|
*
|
|
702
819
|
* @returns {Set}
|
|
703
820
|
* @throws {TypeError} If `names` is not a valid value
|
|
704
821
|
*/
|
|
705
|
-
|
|
822
|
+
getEventTypes(types)
|
|
706
823
|
{
|
|
707
|
-
if (typeof
|
|
824
|
+
if (typeof types === S)
|
|
825
|
+
{
|
|
826
|
+
return this.splitNames(types);
|
|
827
|
+
}
|
|
828
|
+
else if (typeof types === SY)
|
|
708
829
|
{
|
|
709
|
-
return
|
|
830
|
+
return new Set([types]);
|
|
710
831
|
}
|
|
711
|
-
else if (
|
|
832
|
+
else if (types instanceof Set)
|
|
712
833
|
{
|
|
713
|
-
return
|
|
834
|
+
return types;
|
|
714
835
|
}
|
|
715
|
-
else if (isIterable(
|
|
836
|
+
else if (isIterable(types))
|
|
716
837
|
{
|
|
717
|
-
return new Set(
|
|
838
|
+
return new Set(types);
|
|
718
839
|
}
|
|
719
840
|
else
|
|
720
841
|
{
|
|
721
|
-
console.error({names, registry: this});
|
|
842
|
+
console.error({names: types, registry: this});
|
|
722
843
|
throw new TypeError("Invalid event names");
|
|
723
844
|
}
|
|
724
845
|
}
|
|
@@ -735,9 +856,20 @@ class LumEventRegistry
|
|
|
735
856
|
|
|
736
857
|
}
|
|
737
858
|
|
|
738
|
-
|
|
859
|
+
const LERP = LumEventRegistry.prototype;
|
|
860
|
+
def(LERP, 'getEventNames', LERP.getEventTypes);
|
|
861
|
+
|
|
862
|
+
cp(LumEventRegistry,
|
|
739
863
|
{
|
|
740
864
|
isRegistered, getMetadata, targetsAre,
|
|
741
865
|
});
|
|
742
866
|
|
|
743
867
|
module.exports = LumEventRegistry;
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* An alias to `getEventTypes`
|
|
871
|
+
* @function module:@lumjs/core/events.Registry#getEventNames
|
|
872
|
+
* @param {(string|symbol|Iterable)} names - Event names source
|
|
873
|
+
* @returns {Set}
|
|
874
|
+
* @see module:@lumjs/core/events.Registry#getEventTypes
|
|
875
|
+
*/
|
package/lib/obj/clone.js
CHANGED
|
@@ -296,6 +296,9 @@ exports.addClone = addClone;
|
|
|
296
296
|
*
|
|
297
297
|
* If not, if the object has a `clone()` method it will be used.
|
|
298
298
|
* Otherwise use our `clone()` function.
|
|
299
|
+
*
|
|
300
|
+
* **NOTE**: A newer replacement for this function exists, see
|
|
301
|
+
* {@link module:@lumjs/core/obj.unlocked} for details.
|
|
299
302
|
*
|
|
300
303
|
* @param {object} obj - The object to clone if needed.
|
|
301
304
|
* @param {object} [opts] - Options to pass to `clone()` method.
|
package/lib/obj/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const {lock,addLock} = require('./lock');
|
|
|
14
14
|
const {mergeNested,syncNested} = require('./merge');
|
|
15
15
|
const ns = require('./ns');
|
|
16
16
|
const cp = require('./cp');
|
|
17
|
+
const unlocked = require('./unlocked');
|
|
17
18
|
|
|
18
19
|
const
|
|
19
20
|
{
|
|
@@ -27,5 +28,5 @@ module.exports =
|
|
|
27
28
|
mergeNested, syncNested, copyProps, copyAll, ns,
|
|
28
29
|
getObjectPath, setObjectPath, getNamespace, setNamespace,
|
|
29
30
|
getProperty, duplicateAll, duplicateOne, getMethods, signatureOf,
|
|
30
|
-
MethodFilter, apply, flip, flipKeyVal, flipMap,
|
|
31
|
+
MethodFilter, apply, flip, flipKeyVal, flipMap, unlocked,
|
|
31
32
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {F,S,isObj,needObj} = require('../types');
|
|
4
|
+
|
|
5
|
+
const CLONE = 'clone';
|
|
6
|
+
const clone = obj => Object.assign({}, obj);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get an unlocked object
|
|
10
|
+
*
|
|
11
|
+
* @param {object} obj - The target object;
|
|
12
|
+
*
|
|
13
|
+
* If the object is extensible it will be returned _as is_.
|
|
14
|
+
*
|
|
15
|
+
* If the object is frozen, sealed, or otherwise non-extensible,
|
|
16
|
+
* a cloning function will be used to make an unlocked copy.
|
|
17
|
+
*
|
|
18
|
+
* @param {(object|function|string)} [opts] Options to customize the behaviour
|
|
19
|
+
*
|
|
20
|
+
* - If this is a `function` it will be used as the `opts.fn` value.
|
|
21
|
+
* - If this is a `string` it will be used as the `opts.method` value.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} [opts.method='clone'] Object method to use
|
|
24
|
+
*
|
|
25
|
+
* Set to an empty string `""` to skip checking for an object method.
|
|
26
|
+
*
|
|
27
|
+
* If the method exists on the target object, it will be called to
|
|
28
|
+
* perform the cloning procedure, otherwise `opts.fn` will be used.
|
|
29
|
+
*
|
|
30
|
+
* @param {Array} [opts.args] Arguments for `opts.method` method
|
|
31
|
+
*
|
|
32
|
+
* If not specified, the default value is: `[opts]`
|
|
33
|
+
*
|
|
34
|
+
* @param {function} [opts.fn] A function to perform the clone operation
|
|
35
|
+
*
|
|
36
|
+
* Must take only a single parameter, which is the object to clone.
|
|
37
|
+
* Must return an extensible clone of the object.
|
|
38
|
+
*
|
|
39
|
+
* If for whatever reason you need the `opts` in the function,
|
|
40
|
+
* then use a unbound function, and `opts` will be available
|
|
41
|
+
* as `this` in the function body. Arrow functions (closures) are
|
|
42
|
+
* always considered bound and cannot have a `this` value assigned.
|
|
43
|
+
*
|
|
44
|
+
* The default value is a closure: `obj => Object.assign({}, obj)`;
|
|
45
|
+
*
|
|
46
|
+
* @return {object}
|
|
47
|
+
*
|
|
48
|
+
* @alias module:@lumjs/core/obj.unlocked
|
|
49
|
+
*/
|
|
50
|
+
function unlocked(obj, opts={})
|
|
51
|
+
{
|
|
52
|
+
needObj(obj, true, "invalid obj value");
|
|
53
|
+
|
|
54
|
+
if (Object.isExtensible(obj))
|
|
55
|
+
{ // We're done here.
|
|
56
|
+
return obj;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof opts === F)
|
|
60
|
+
{
|
|
61
|
+
opts = {fn: opts}
|
|
62
|
+
}
|
|
63
|
+
else if (typeof opts === S)
|
|
64
|
+
{
|
|
65
|
+
opts = {method: opts}
|
|
66
|
+
}
|
|
67
|
+
else if (!isObj(opts))
|
|
68
|
+
{
|
|
69
|
+
console.error({obj, opts});
|
|
70
|
+
throw new TypeError("invalid opts value");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const fn = (typeof opts.fn === F) ? opts.fn : clone;
|
|
74
|
+
const meth = (typeof opts.method === S) ? opts.method.trim() : CLONE;
|
|
75
|
+
const args = (Array.isArray(opts.args)) ? opts.args : [opts];
|
|
76
|
+
|
|
77
|
+
if (meth && typeof obj[meth] === F)
|
|
78
|
+
{ // Use a clone method
|
|
79
|
+
return obj[meth](...args);
|
|
80
|
+
}
|
|
81
|
+
else
|
|
82
|
+
{ // Use a clone function
|
|
83
|
+
return fn.call(opts, obj);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = unlocked;
|
package/lib/types/isa.js
CHANGED