@lumjs/core 1.34.0 → 1.35.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/index.js +38 -1
- package/lib/events/observable.js +49 -7
- package/lib/events/registry.js +9 -12
- package/lib/observable.js +62 -319
- package/lib/old/observable.js +291 -0
- package/lib/traits.js +83 -66
- package/lib/types/basics.js +34 -1
- package/package.json +1 -1
package/lib/events/index.js
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module @lumjs/core/events
|
|
12
12
|
*/
|
|
13
|
+
|
|
14
|
+
const lazy = require('../types/lazy');
|
|
15
|
+
|
|
13
16
|
exports = module.exports =
|
|
14
17
|
{
|
|
15
18
|
Registry: require('./registry'),
|
|
@@ -50,7 +53,41 @@ exports = module.exports =
|
|
|
50
53
|
*/
|
|
51
54
|
observable(target, opts)
|
|
52
55
|
{
|
|
53
|
-
const {makeObservable} =
|
|
56
|
+
const {makeObservable} = exports.Observable;
|
|
54
57
|
return makeObservable(target, opts);
|
|
55
58
|
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* See if an object has a known trigger method.
|
|
62
|
+
*
|
|
63
|
+
* Currently will look for one of:
|
|
64
|
+
*
|
|
65
|
+
* - `emit`
|
|
66
|
+
* - `trigger`
|
|
67
|
+
*
|
|
68
|
+
* @param {object} obj - Object we want to find a trigger method on
|
|
69
|
+
*
|
|
70
|
+
* @returns {?string} The name of the method found,
|
|
71
|
+
* or null if none found.
|
|
72
|
+
*/
|
|
73
|
+
hasTrigger(obj)
|
|
74
|
+
{
|
|
75
|
+
for (const trigger of exports.KNOWN_TRIGGERS)
|
|
76
|
+
{
|
|
77
|
+
if (typeof obj[trigger] === F)
|
|
78
|
+
{
|
|
79
|
+
return trigger;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The list of known trigger method names for `hasTrigger()`
|
|
88
|
+
*/
|
|
89
|
+
KNOWN_TRIGGERS: ['emit','trigger'],
|
|
90
|
+
|
|
56
91
|
}
|
|
92
|
+
|
|
93
|
+
lazy(exports, 'Observable', () => require('./observable'));
|
package/lib/events/observable.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
"use strict";
|
|
6
6
|
|
|
7
|
-
const {B,F,S,def} = require('../types');
|
|
7
|
+
const {B,F,S,def,isComplex,TYPES} = require('../types');
|
|
8
8
|
const lock = Object.freeze;
|
|
9
9
|
const copy = Object.assign;
|
|
10
10
|
const Registry = require('./registry');
|
|
@@ -41,12 +41,10 @@ const OBS_OPTIONS =
|
|
|
41
41
|
* A few obscure features that I don't think are particularly
|
|
42
42
|
* important and won't affect *most* exising uses will be dropped.
|
|
43
43
|
*
|
|
44
|
-
* @param {(object|function)} el - The target
|
|
45
|
-
* @param {object} [oo]
|
|
46
|
-
* {@link module:@lumjs/core/observable Observable} options;
|
|
44
|
+
* @param {(object|function)} el - The target to add observable API to.
|
|
47
45
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
46
|
+
* @param {object} [oo]
|
|
47
|
+
* {@link module:@lumjs/core/observable Observable} options
|
|
50
48
|
*
|
|
51
49
|
* @param {boolean} [oo.addwrap] If this is `true`,
|
|
52
50
|
* and `oo.wrapargs` is `false`, then the `event` object
|
|
@@ -69,10 +67,16 @@ const OBS_OPTIONS =
|
|
|
69
67
|
*
|
|
70
68
|
* @param {boolean} [wantReg=false] Return the Registry?
|
|
71
69
|
* @returns {object} normally `el`, unless `wantReg` was `true`
|
|
70
|
+
* @see module:@lumjs/core/observable~API
|
|
72
71
|
* @alias module:@lumjs/core/events/observable.makeObservable
|
|
73
72
|
*/
|
|
74
73
|
function makeObservable(el, oo, ro, wantReg=false)
|
|
75
74
|
{
|
|
75
|
+
if (!isComplex(el))
|
|
76
|
+
{
|
|
77
|
+
throw new TypeError("el was not an object or function");
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
ro = copy({}, ro);
|
|
77
81
|
ro.extend = copy({}, OBS_EXTENDS, ro.extend);
|
|
78
82
|
ro.setupEvent = setupEvent;
|
|
@@ -126,6 +130,7 @@ function makeObservable(el, oo, ro, wantReg=false)
|
|
|
126
130
|
/**
|
|
127
131
|
* Event setup for observable compatibility
|
|
128
132
|
* @type {module:@lumjs/core/events~SetupEvent}
|
|
133
|
+
* @alias module:@lumjs/core/events/observable.setupEvent
|
|
129
134
|
*/
|
|
130
135
|
function setupEvent(ev)
|
|
131
136
|
{
|
|
@@ -150,6 +155,7 @@ function setupEvent(ev)
|
|
|
150
155
|
/**
|
|
151
156
|
* Listener setup for observable compatibility
|
|
152
157
|
* @type {module:@lumjs/core/events~SetupListener}
|
|
158
|
+
* @alias module:@lumjs/core/events/observable.setupListener
|
|
153
159
|
*/
|
|
154
160
|
function setupListener(ln)
|
|
155
161
|
{
|
|
@@ -195,7 +201,43 @@ function setupListener(ln)
|
|
|
195
201
|
}
|
|
196
202
|
}
|
|
197
203
|
|
|
204
|
+
/**
|
|
205
|
+
* See if a value appears to implement the observable API.
|
|
206
|
+
*
|
|
207
|
+
* This is a simple function that relies on duck-typing,
|
|
208
|
+
* and only looks for `trigger` and `on` methods.
|
|
209
|
+
*
|
|
210
|
+
* @param {(object|function)} obj - Value to check for observable API
|
|
211
|
+
* @returns {boolean}
|
|
212
|
+
* @alias module:@lumjs/core/events/observable.isObservable
|
|
213
|
+
*/
|
|
214
|
+
function isObservable(obj)
|
|
215
|
+
{
|
|
216
|
+
return (isComplex(obj)
|
|
217
|
+
&& typeof obj.trigger === F
|
|
218
|
+
&& typeof obj.on === F);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Undocumented alias for the sake of compatibility.
|
|
222
|
+
def(makeObservable, 'is', isObservable);
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Does a value implement the Observable interface?
|
|
226
|
+
* @name module:@lumjs/core/types.doesObservable
|
|
227
|
+
* @function
|
|
228
|
+
* @param {*} v - The expected object/function to test.
|
|
229
|
+
* @returns {boolean}
|
|
230
|
+
* @see module:@lumjs/core/events/observable.isObservable
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Extension type for the {@link module:@lumjs/core/observable~API} interface.
|
|
235
|
+
* @memberof module:@lumjs/core/types.TYPES
|
|
236
|
+
* @member {string} OBSERV - Implements the *Observable* interface.
|
|
237
|
+
*/
|
|
238
|
+
TYPES.add('OBSERV', 'observable', isObservable, 'doesObservable');
|
|
239
|
+
|
|
198
240
|
module.exports =
|
|
199
241
|
{
|
|
200
|
-
makeObservable, setupEvent, setupListener,
|
|
242
|
+
makeObservable, setupEvent, setupListener, isObservable,
|
|
201
243
|
}
|
package/lib/events/registry.js
CHANGED
|
@@ -225,19 +225,16 @@ class LumEventRegistry
|
|
|
225
225
|
const value = iname === 'registry'
|
|
226
226
|
? this // The registry instance itself
|
|
227
227
|
: (...args) => this[iname](...args) // A proxy method
|
|
228
|
-
|
|
228
|
+
if (opts.overwrite || target[ename] === undefined)
|
|
229
229
|
{
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
console.error("Won't overwrite existing property",
|
|
239
|
-
{target,iname,ename,registry: this});
|
|
240
|
-
}
|
|
230
|
+
def(target, ename, {value});
|
|
231
|
+
tps[ename] = iname;
|
|
232
|
+
tpm.p[ename] = this;
|
|
233
|
+
}
|
|
234
|
+
else
|
|
235
|
+
{
|
|
236
|
+
console.error("Won't overwrite existing property",
|
|
237
|
+
{target,iname,ename,registry: this});
|
|
241
238
|
}
|
|
242
239
|
}
|
|
243
240
|
}
|
package/lib/observable.js
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Observable API module
|
|
3
|
+
* @module @lumjs/core/observable
|
|
4
|
+
*/
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
const {B,S,def} = require('./types');
|
|
8
|
+
const orig = require('./old/observable');
|
|
9
|
+
const evob = require('./events/observable');
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
|
-
* Make a target object (or function) support the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* Make a target object (or function) support the observable API.
|
|
13
|
+
*
|
|
14
|
+
* There are currently two implementations of the observable API,
|
|
15
|
+
* and this function will determine which one to call based on the
|
|
16
|
+
* arguments passed (if old options or positional arguments are
|
|
17
|
+
* detected, the original implementation will be called, otherwise
|
|
18
|
+
* the new events-based implementation will be called.)
|
|
10
19
|
*
|
|
11
|
-
*
|
|
20
|
+
* When the older implementation is removed, this function will be
|
|
21
|
+
* replaced by a simple alias to `makeObservable`.
|
|
22
|
+
*
|
|
23
|
+
* NOTE: this function is the *actual* exported value of the `observable`
|
|
24
|
+
* module, but it's also available as `observable.auto` and that is how
|
|
25
|
+
* it's being documented (as jsdoc doesn't like functions being treated
|
|
26
|
+
* like objects with methods defined on them, but I do that a lot.)
|
|
27
|
+
*
|
|
28
|
+
* @param {(object|function)} el - The target to add observable API to.
|
|
12
29
|
* @param {object} [opts] Options that define behaviours.
|
|
30
|
+
*
|
|
31
|
+
* If the `addre` and `reinherit` options are detected, then the original
|
|
32
|
+
* implementation will be used.
|
|
33
|
+
*
|
|
13
34
|
* @param {string} [opts.wildcard='*'] The event name used as a wildcard.
|
|
14
35
|
*
|
|
15
36
|
* @param {boolean} [opts.wrapargs=false] If `true`, the event handlers will
|
|
@@ -76,335 +97,57 @@ const copy = Object.assign;
|
|
|
76
97
|
* to the `el` object, which is a version of `observable()` with the
|
|
77
98
|
* default options being the same as the current `opts`.
|
|
78
99
|
*
|
|
79
|
-
* @param {
|
|
80
|
-
* to the `el` object, which is a function that can re-build the
|
|
81
|
-
* observable API methods with new `opts` replacing the old ones.
|
|
82
|
-
*
|
|
83
|
-
* The method added takes two arguments, the first being an `object`
|
|
84
|
-
* representing the new options to set on the target.
|
|
100
|
+
* @param {(boolean|object)} [arg3] Version dependent argument.
|
|
85
101
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* specified in the first argument.
|
|
102
|
+
* If this is a `boolean` then the original observable implementation
|
|
103
|
+
* will be called and this will be the `redefine` argument.
|
|
89
104
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
105
|
+
* Otherwise it's assumed to be the `ro` argument of the `makeObservable`
|
|
106
|
+
* compatibility implementation.
|
|
92
107
|
*
|
|
93
|
-
* @param {boolean} [redefine=false] If `true` allow targets already
|
|
94
|
-
* implementing the `on()` and `trigger()` methods to be re-initialized.
|
|
95
|
-
*
|
|
96
|
-
* Generally only needed if you need to change the `opts` for some reason.
|
|
97
|
-
* This is forced to `true` by the method added by `opts.addre`.
|
|
98
|
-
*
|
|
99
108
|
* @returns {object} el
|
|
100
109
|
*
|
|
101
|
-
* @
|
|
110
|
+
* @see module:@lumjs/core/observable.orig
|
|
111
|
+
* @see module:@lumjs/core/events/observable.makeObservable
|
|
112
|
+
* @alias module:@lumjs/core/observable.auto
|
|
102
113
|
*/
|
|
103
|
-
function observable (el={}, opts={},
|
|
114
|
+
function observable (el={}, opts={}, arg3)
|
|
104
115
|
{
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
{ // Don't know how to handle this, sorry.
|
|
109
|
-
throw new Error("non-object sent to observable()");
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (isObservable(el) && !redefine)
|
|
113
|
-
{ // It's already observable.
|
|
114
|
-
return el;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (typeof opts === B)
|
|
118
|
-
{ // Assume it's the wrapthis option.
|
|
119
|
-
opts = {wrapthis: opts};
|
|
120
|
-
}
|
|
121
|
-
else if (!isObj(opts))
|
|
122
|
-
{
|
|
123
|
-
opts = {};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const noSpace = /^\S+$/;
|
|
127
|
-
|
|
128
|
-
const wildcard = (typeof opts.wildcard === S
|
|
129
|
-
&& noSpace.test(opts.wildcard))
|
|
130
|
-
? opts.wildcard
|
|
131
|
-
: '*';
|
|
132
|
-
|
|
133
|
-
const wrapsetup = (typeof opts.wrapsetup === F)
|
|
134
|
-
? opts.wrapsetup
|
|
135
|
-
: null;
|
|
136
|
-
|
|
137
|
-
const wrapthis = (typeof opts.wrapthis === B)
|
|
138
|
-
? opts.wrapthis
|
|
139
|
-
: false;
|
|
140
|
-
|
|
141
|
-
const wrapargs = (typeof opts.wrapargs === B)
|
|
142
|
-
? opts.wrapargs
|
|
143
|
-
: (wrapsetup ? !wrapthis : false);
|
|
144
|
-
|
|
145
|
-
const wrapped = (wrapthis || wrapargs);
|
|
146
|
-
|
|
147
|
-
const wraplock = (typeof opts.wraplock === B)
|
|
148
|
-
? opts.wraplock
|
|
149
|
-
: true;
|
|
150
|
-
|
|
151
|
-
const addname = (typeof opts.addname === B)
|
|
152
|
-
? opts.addname
|
|
153
|
-
: !wrapped;
|
|
154
|
-
|
|
155
|
-
const addis = (typeof opts.addis === B)
|
|
156
|
-
? opts.addis
|
|
157
|
-
: wrapped;
|
|
158
|
-
|
|
159
|
-
const validIdent = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
|
|
160
|
-
|
|
161
|
-
const addme = (typeof opts.addme === S
|
|
162
|
-
&& validIdent.test(opts.addme))
|
|
163
|
-
? opts.addme
|
|
164
|
-
: null;
|
|
165
|
-
|
|
166
|
-
const addre = (typeof opts.addre === S
|
|
167
|
-
&& validIdent.test(opts.addre))
|
|
168
|
-
? opts.addre
|
|
169
|
-
: null;
|
|
170
|
-
|
|
171
|
-
const slice = Array.prototype.slice;
|
|
172
|
-
|
|
173
|
-
function onEachEvent (e, fn)
|
|
116
|
+
if (typeof arg3 === B
|
|
117
|
+
|| typeof opts.addre === S
|
|
118
|
+
|| typeof opts.reinherit === B)
|
|
174
119
|
{
|
|
175
|
-
|
|
176
|
-
const me = es.length > 1;
|
|
177
|
-
for (e of es)
|
|
178
|
-
{
|
|
179
|
-
fn(e, me);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const add = def(el);
|
|
184
|
-
|
|
185
|
-
function runCallback (name, fn, args)
|
|
186
|
-
{
|
|
187
|
-
if (fn.busy) return;
|
|
188
|
-
fn.busy = 1;
|
|
189
|
-
|
|
190
|
-
let fobj;
|
|
191
|
-
|
|
192
|
-
if (wrapped)
|
|
193
|
-
{ // Something is going to use our wrapper object.
|
|
194
|
-
const isWild = (name === wildcard);
|
|
195
|
-
const fname = isWild ? (addname ? args[0] : args.shift()) : name;
|
|
196
|
-
|
|
197
|
-
fobj =
|
|
198
|
-
{
|
|
199
|
-
isObservable: lock({event: true, target: false}),
|
|
200
|
-
self: el,
|
|
201
|
-
target: el,
|
|
202
|
-
name: fname,
|
|
203
|
-
type: fname,
|
|
204
|
-
func: fn,
|
|
205
|
-
wildcard: isWild,
|
|
206
|
-
args,
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
if (wrapsetup)
|
|
210
|
-
{
|
|
211
|
-
wrapsetup.call(el, fobj);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (wraplock)
|
|
215
|
-
{
|
|
216
|
-
lock(fobj);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const fthis = wrapthis ? fobj : el;
|
|
221
|
-
const fargs = wrapargs ? [fobj]
|
|
222
|
-
: ((fn.typed && addname) ? [name].concat(args) : args);
|
|
223
|
-
|
|
224
|
-
fn.apply(fthis, fargs);
|
|
225
|
-
fn.busy = 0;
|
|
120
|
+
return orig(el, opts, arg3);
|
|
226
121
|
}
|
|
227
|
-
|
|
228
|
-
let callbacks = {};
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Assign an event handler
|
|
232
|
-
*
|
|
233
|
-
* Listen to the given space separated list of `events` and execute
|
|
234
|
-
* the `callback` each time an event is triggered.
|
|
235
|
-
* @param {string} events - events ids
|
|
236
|
-
* @param {function} fn - callback function
|
|
237
|
-
* @returns {object} el
|
|
238
|
-
*/
|
|
239
|
-
add('on', function(events, fn)
|
|
240
|
-
{
|
|
241
|
-
if (typeof fn !== F)
|
|
242
|
-
{
|
|
243
|
-
console.error("non-function passed to on()");
|
|
244
|
-
return el;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
onEachEvent(events, function(name, typed)
|
|
248
|
-
{
|
|
249
|
-
(callbacks[name] = callbacks[name] || []).push(fn);
|
|
250
|
-
fn.typed = typed;
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
return el;
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Removes the given space separated list of `events` listeners
|
|
258
|
-
*
|
|
259
|
-
* @param {string} events - events ids
|
|
260
|
-
* @param {function} fn - callback function
|
|
261
|
-
* @returns {object} el
|
|
262
|
-
*/
|
|
263
|
-
add('off', function(events, fn)
|
|
264
|
-
{
|
|
265
|
-
if (events === wildcard && !fn)
|
|
266
|
-
{ // Clear all callbacks.
|
|
267
|
-
callbacks = {};
|
|
268
|
-
}
|
|
269
|
-
else
|
|
270
|
-
{
|
|
271
|
-
onEachEvent(events, function(name)
|
|
272
|
-
{
|
|
273
|
-
if (fn)
|
|
274
|
-
{ // Find a specific callback to remove.
|
|
275
|
-
var arr = callbacks[name]
|
|
276
|
-
for (var i = 0, cb; cb = arr && arr[i]; ++i)
|
|
277
|
-
{
|
|
278
|
-
if (cb == fn) arr.splice(i--, 1);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
else
|
|
282
|
-
{ // Remove all callbacks for this event.
|
|
283
|
-
delete callbacks[name];
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
return el
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Add a one-shot event handler.
|
|
292
|
-
*
|
|
293
|
-
* Listen to the given space separated list of `events` and execute
|
|
294
|
-
* the `callback` at most once.
|
|
295
|
-
*
|
|
296
|
-
* @param {string} events - events ids
|
|
297
|
-
* @param {function} fn - callback function
|
|
298
|
-
* @returns {object} el
|
|
299
|
-
*/
|
|
300
|
-
add('one', function(events, fn)
|
|
301
|
-
{
|
|
302
|
-
function on()
|
|
303
|
-
{
|
|
304
|
-
el.off(events, on)
|
|
305
|
-
fn.apply(el, arguments)
|
|
306
|
-
}
|
|
307
|
-
return el.on(events, on);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Execute all callback functions that listen to the given space
|
|
312
|
-
* separated list of `events`
|
|
313
|
-
* @param {string} events - events ids
|
|
314
|
-
* @returns {object} el
|
|
315
|
-
*/
|
|
316
|
-
add('trigger', function(events)
|
|
317
|
-
{
|
|
318
|
-
// getting the arguments
|
|
319
|
-
// skipping the first one
|
|
320
|
-
const args = slice.call(arguments, 1);
|
|
321
|
-
|
|
322
|
-
onEachEvent(events, function(name)
|
|
323
|
-
{
|
|
324
|
-
const fns = slice.call(callbacks[name] || [], 0);
|
|
325
|
-
|
|
326
|
-
for (var i = 0, fn; fn = fns[i]; ++i)
|
|
327
|
-
{
|
|
328
|
-
runCallback(name, fn, args);
|
|
329
|
-
if (fns[i] !== fn) { i-- }
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (callbacks[wildcard] && name != wildcard)
|
|
333
|
-
{ // Trigger the wildcard.
|
|
334
|
-
el.trigger.apply(el, ['*', name].concat(args));
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
return el
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
if (addis)
|
|
122
|
+
else
|
|
343
123
|
{
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (addme)
|
|
348
|
-
{ // Add a wrapper for observable() that sets new default options.
|
|
349
|
-
add(addme, function (obj=null, mopts)
|
|
350
|
-
{
|
|
351
|
-
return observable(obj, copy({}, opts, mopts));
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (addre)
|
|
356
|
-
{ // Add a method to change the observable options.
|
|
357
|
-
const reinherit = opts.reinherit ?? false;
|
|
358
|
-
|
|
359
|
-
add(addre, function(replacementOpts={}, inherit=reinherit)
|
|
360
|
-
{
|
|
361
|
-
const newOpts
|
|
362
|
-
= inherit
|
|
363
|
-
? Object.assign({}, opts, replacementOpts)
|
|
364
|
-
: replacementOpts;
|
|
365
|
-
return observable(el, replacementOpts, true);
|
|
366
|
-
});
|
|
124
|
+
return evob.makeObservable(el, opts, arg3);
|
|
367
125
|
}
|
|
368
|
-
|
|
369
|
-
// Metadata
|
|
370
|
-
add('$$observable$$', lock({opts, observable}));
|
|
371
|
-
|
|
372
|
-
return el
|
|
373
|
-
|
|
374
|
-
} // observable()
|
|
126
|
+
}
|
|
375
127
|
|
|
376
128
|
module.exports = observable;
|
|
377
129
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
* @returns {boolean}
|
|
384
|
-
*/
|
|
385
|
-
function isObservable(obj)
|
|
386
|
-
{
|
|
387
|
-
return (isComplex(obj)
|
|
388
|
-
&& typeof obj.trigger === F
|
|
389
|
-
&& typeof obj.on === F);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Add an 'is()' method to `observable` itself.
|
|
393
|
-
def(observable, 'is', isObservable);
|
|
130
|
+
def(observable)
|
|
131
|
+
('is', evob.isObservable)
|
|
132
|
+
('auto', observable)
|
|
133
|
+
('orig', orig)
|
|
134
|
+
('wrap', evob.makeObservable)
|
|
394
135
|
|
|
395
136
|
/**
|
|
396
|
-
* Does a value implement the
|
|
397
|
-
* @
|
|
398
|
-
* @
|
|
399
|
-
* @param {*} v - The expected object/function to test.
|
|
137
|
+
* Does a value implement the observable interface?
|
|
138
|
+
* @function module:@lumjs/core/observable.is
|
|
139
|
+
* @param {*} v - Value to test
|
|
400
140
|
* @returns {boolean}
|
|
401
|
-
* @see module:@lumjs/core/observable.
|
|
141
|
+
* @see module:@lumjs/core/events/observable.isObservable
|
|
402
142
|
*/
|
|
403
143
|
|
|
404
144
|
/**
|
|
405
|
-
*
|
|
406
|
-
* @
|
|
407
|
-
*
|
|
145
|
+
* An alias to
|
|
146
|
+
* {@link module:@lumjs/core/events/observable.makeObservable makeObservable()}
|
|
147
|
+
*
|
|
148
|
+
* @function module:@lumjs/core/observable.wrap
|
|
149
|
+
* @param {(object|function)} el
|
|
150
|
+
* @param {object} oo
|
|
151
|
+
* @param {object} ro
|
|
152
|
+
* @returns {object}
|
|
408
153
|
*/
|
|
409
|
-
|
|
410
|
-
TYPES.add('OBSERV', 'observable', isObservable, 'doesObservable');
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// Defining these after observable.
|
|
2
|
+
const {B,F,S,def,isObj,isComplex} = require('../types');
|
|
3
|
+
const {isObservable} = require('../events/observable');
|
|
4
|
+
const lock = Object.freeze;
|
|
5
|
+
const copy = Object.assign;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The original implementation of the observable API.
|
|
9
|
+
*
|
|
10
|
+
* This older implementation will be removed entirely when `v2.0` is
|
|
11
|
+
* released, and possibly sooner if I don't find anything broken with
|
|
12
|
+
* the compatibility wrapper. For now it's able to be loaded on demand.
|
|
13
|
+
*
|
|
14
|
+
* @param {(object|function)} el - The target to add observable API to.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} [opts]
|
|
17
|
+
* {@link module:@lumjs/core/observable.observable Observable} options.
|
|
18
|
+
*
|
|
19
|
+
* Includes a few additional options no longer supported by the
|
|
20
|
+
* new events-based implementation. Only the options exclusive to this
|
|
21
|
+
* version will be documented here.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} [opts.addre] If set, add a method with this name
|
|
24
|
+
* to the `el` object, which is a function that can re-build the
|
|
25
|
+
* observable API methods with new `opts` replacing the old ones.
|
|
26
|
+
*
|
|
27
|
+
* The method added takes two arguments, the first being an `object`
|
|
28
|
+
* representing the new options to set on the target.
|
|
29
|
+
*
|
|
30
|
+
* The second is an optional `boolean` value that determines if the
|
|
31
|
+
* existing `opts` should be used as defaults for any options not
|
|
32
|
+
* specified in the first argument.
|
|
33
|
+
*
|
|
34
|
+
* @param {boolean} [opts.reinherit=false] Used as the default value of
|
|
35
|
+
* the second argument of the method added by `opts.addre`.
|
|
36
|
+
*
|
|
37
|
+
* @param {boolean} [redefine=false] If `true` allow targets already
|
|
38
|
+
* implementing the `on()` and `trigger()` methods to be re-initialized.
|
|
39
|
+
*
|
|
40
|
+
* Generally only needed if you need to change the `opts` for some reason.
|
|
41
|
+
* This is forced to `true` by the method added by `opts.addre`.
|
|
42
|
+
*
|
|
43
|
+
* @returns {object} el
|
|
44
|
+
* @see module:@lumjs/core/observable~API
|
|
45
|
+
* @alias module:@lumjs/core/observable.orig
|
|
46
|
+
*/
|
|
47
|
+
function observable (el={}, opts={}, redefine=false)
|
|
48
|
+
{
|
|
49
|
+
//console.debug("observable", el, opts);
|
|
50
|
+
|
|
51
|
+
if (!isComplex(el))
|
|
52
|
+
{ // Don't know how to handle this, sorry.
|
|
53
|
+
throw new Error("non-object sent to observable()");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (isObservable(el) && !redefine)
|
|
57
|
+
{ // It's already observable.
|
|
58
|
+
return el;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof opts === B)
|
|
62
|
+
{ // Assume it's the wrapthis option.
|
|
63
|
+
opts = {wrapthis: opts};
|
|
64
|
+
}
|
|
65
|
+
else if (!isObj(opts))
|
|
66
|
+
{
|
|
67
|
+
opts = {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const noSpace = /^\S+$/;
|
|
71
|
+
|
|
72
|
+
const wildcard = (typeof opts.wildcard === S
|
|
73
|
+
&& noSpace.test(opts.wildcard))
|
|
74
|
+
? opts.wildcard
|
|
75
|
+
: '*';
|
|
76
|
+
|
|
77
|
+
const wrapsetup = (typeof opts.wrapsetup === F)
|
|
78
|
+
? opts.wrapsetup
|
|
79
|
+
: null;
|
|
80
|
+
|
|
81
|
+
const wrapthis = (typeof opts.wrapthis === B)
|
|
82
|
+
? opts.wrapthis
|
|
83
|
+
: false;
|
|
84
|
+
|
|
85
|
+
const wrapargs = (typeof opts.wrapargs === B)
|
|
86
|
+
? opts.wrapargs
|
|
87
|
+
: (wrapsetup ? !wrapthis : false);
|
|
88
|
+
|
|
89
|
+
const wrapped = (wrapthis || wrapargs);
|
|
90
|
+
|
|
91
|
+
const wraplock = (typeof opts.wraplock === B)
|
|
92
|
+
? opts.wraplock
|
|
93
|
+
: true;
|
|
94
|
+
|
|
95
|
+
const addname = (typeof opts.addname === B)
|
|
96
|
+
? opts.addname
|
|
97
|
+
: !wrapped;
|
|
98
|
+
|
|
99
|
+
const addis = (typeof opts.addis === B)
|
|
100
|
+
? opts.addis
|
|
101
|
+
: wrapped;
|
|
102
|
+
|
|
103
|
+
const validIdent = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
|
|
104
|
+
|
|
105
|
+
const addme = (typeof opts.addme === S
|
|
106
|
+
&& validIdent.test(opts.addme))
|
|
107
|
+
? opts.addme
|
|
108
|
+
: null;
|
|
109
|
+
|
|
110
|
+
const addre = (typeof opts.addre === S
|
|
111
|
+
&& validIdent.test(opts.addre))
|
|
112
|
+
? opts.addre
|
|
113
|
+
: null;
|
|
114
|
+
|
|
115
|
+
const slice = Array.prototype.slice;
|
|
116
|
+
|
|
117
|
+
function onEachEvent (e, fn)
|
|
118
|
+
{
|
|
119
|
+
const es = e.split(/\s+/);
|
|
120
|
+
const me = es.length > 1;
|
|
121
|
+
for (e of es)
|
|
122
|
+
{
|
|
123
|
+
fn(e, me);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const add = def(el);
|
|
128
|
+
|
|
129
|
+
function runCallback (name, fn, args)
|
|
130
|
+
{
|
|
131
|
+
if (fn.busy) return;
|
|
132
|
+
fn.busy = 1;
|
|
133
|
+
|
|
134
|
+
let fobj;
|
|
135
|
+
|
|
136
|
+
if (wrapped)
|
|
137
|
+
{ // Something is going to use our wrapper object.
|
|
138
|
+
const isWild = (name === wildcard);
|
|
139
|
+
const fname = isWild ? (addname ? args[0] : args.shift()) : name;
|
|
140
|
+
|
|
141
|
+
fobj =
|
|
142
|
+
{
|
|
143
|
+
isObservable: lock({event: true, target: false}),
|
|
144
|
+
self: el,
|
|
145
|
+
target: el,
|
|
146
|
+
name: fname,
|
|
147
|
+
type: fname,
|
|
148
|
+
func: fn,
|
|
149
|
+
wildcard: isWild,
|
|
150
|
+
args,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (wrapsetup)
|
|
154
|
+
{
|
|
155
|
+
wrapsetup.call(el, fobj);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (wraplock)
|
|
159
|
+
{
|
|
160
|
+
lock(fobj);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const fthis = wrapthis ? fobj : el;
|
|
165
|
+
const fargs = wrapargs ? [fobj]
|
|
166
|
+
: ((fn.typed && addname) ? [name].concat(args) : args);
|
|
167
|
+
|
|
168
|
+
fn.apply(fthis, fargs);
|
|
169
|
+
fn.busy = 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let callbacks = {};
|
|
173
|
+
|
|
174
|
+
add('on', function(events, fn)
|
|
175
|
+
{
|
|
176
|
+
if (typeof fn !== F)
|
|
177
|
+
{
|
|
178
|
+
console.error("non-function passed to on()");
|
|
179
|
+
return el;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
onEachEvent(events, function(name, typed)
|
|
183
|
+
{
|
|
184
|
+
(callbacks[name] = callbacks[name] || []).push(fn);
|
|
185
|
+
fn.typed = typed;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return el;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
add('off', function(events, fn)
|
|
192
|
+
{
|
|
193
|
+
if (events === wildcard && !fn)
|
|
194
|
+
{ // Clear all callbacks.
|
|
195
|
+
callbacks = {};
|
|
196
|
+
}
|
|
197
|
+
else
|
|
198
|
+
{
|
|
199
|
+
onEachEvent(events, function(name)
|
|
200
|
+
{
|
|
201
|
+
if (fn)
|
|
202
|
+
{ // Find a specific callback to remove.
|
|
203
|
+
var arr = callbacks[name]
|
|
204
|
+
for (var i = 0, cb; cb = arr && arr[i]; ++i)
|
|
205
|
+
{
|
|
206
|
+
if (cb == fn) arr.splice(i--, 1);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else
|
|
210
|
+
{ // Remove all callbacks for this event.
|
|
211
|
+
delete callbacks[name];
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return el
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
add('one', function(events, fn)
|
|
219
|
+
{
|
|
220
|
+
function on()
|
|
221
|
+
{
|
|
222
|
+
el.off(events, on)
|
|
223
|
+
fn.apply(el, arguments)
|
|
224
|
+
}
|
|
225
|
+
return el.on(events, on);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
add('trigger', function(events)
|
|
229
|
+
{
|
|
230
|
+
// getting the arguments
|
|
231
|
+
// skipping the first one
|
|
232
|
+
const args = slice.call(arguments, 1);
|
|
233
|
+
|
|
234
|
+
onEachEvent(events, function(name)
|
|
235
|
+
{
|
|
236
|
+
const fns = slice.call(callbacks[name] || [], 0);
|
|
237
|
+
|
|
238
|
+
for (var i = 0, fn; fn = fns[i]; ++i)
|
|
239
|
+
{
|
|
240
|
+
runCallback(name, fn, args);
|
|
241
|
+
if (fns[i] !== fn) { i-- }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (callbacks[wildcard] && name != wildcard)
|
|
245
|
+
{ // Trigger the wildcard.
|
|
246
|
+
el.trigger.apply(el, ['*', name].concat(args));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return el
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (addis)
|
|
255
|
+
{
|
|
256
|
+
add('isObservable', lock({event: false, target: true}));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (addme)
|
|
260
|
+
{ // Add a wrapper for observable() that sets new default options.
|
|
261
|
+
add(addme, function (obj=null, mopts)
|
|
262
|
+
{
|
|
263
|
+
return observable(obj, copy({}, opts, mopts));
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (addre)
|
|
268
|
+
{ // Add a method to change the observable options.
|
|
269
|
+
const reinherit = opts.reinherit ?? false;
|
|
270
|
+
|
|
271
|
+
add(addre, function(replacementOpts={}, inherit=reinherit)
|
|
272
|
+
{
|
|
273
|
+
const newOpts
|
|
274
|
+
= inherit
|
|
275
|
+
? Object.assign({}, opts, replacementOpts)
|
|
276
|
+
: replacementOpts;
|
|
277
|
+
return observable(el, replacementOpts, true);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Metadata
|
|
282
|
+
add('$$observable$$', lock({opts, observable}));
|
|
283
|
+
|
|
284
|
+
return el
|
|
285
|
+
|
|
286
|
+
} // observable()
|
|
287
|
+
|
|
288
|
+
// Add an 'is()' method to `observable` itself.
|
|
289
|
+
def(observable, 'is', isObservable);
|
|
290
|
+
|
|
291
|
+
module.exports = observable;
|
package/lib/traits.js
CHANGED
|
@@ -10,13 +10,18 @@ const getProp = require('./obj/getproperty');
|
|
|
10
10
|
const
|
|
11
11
|
{
|
|
12
12
|
def,F,B,S,
|
|
13
|
-
isObj,isArray,isConstructor,isProperty,
|
|
13
|
+
isObj,isArray,isConstructor,isProperty,isClassObject,
|
|
14
14
|
needObj,needType,
|
|
15
15
|
} = require('./types');
|
|
16
16
|
|
|
17
17
|
// Symbol for private storage of composed traits.
|
|
18
18
|
const COMPOSED_TRAITS = Symbol.for('@lumjs/core/traits~ComposedTraits');
|
|
19
19
|
|
|
20
|
+
const FLAG_ET = 1; // Flag for ensure target
|
|
21
|
+
const FLAG_ES = 2; // Flag for ensure source
|
|
22
|
+
const FLAGS_S = 0; // Default flags for static mode
|
|
23
|
+
const FLAGS_O = 3; // Default flags for object mode
|
|
24
|
+
|
|
20
25
|
/**
|
|
21
26
|
* For when we need a prototype object exclusively.
|
|
22
27
|
*
|
|
@@ -30,7 +35,7 @@ const COMPOSED_TRAITS = Symbol.for('@lumjs/core/traits~ComposedTraits');
|
|
|
30
35
|
* If `from` is a `function` then `from.prototype` will be returned.
|
|
31
36
|
*
|
|
32
37
|
* @throws {TypeError} If `from` was not a valid value;
|
|
33
|
-
* including if it is a
|
|
38
|
+
* including if it is a function but does not have a valid
|
|
34
39
|
* prototype object to return.
|
|
35
40
|
*
|
|
36
41
|
* @alias module:@lumjs/core/traits.ensureProto
|
|
@@ -47,7 +52,42 @@ function ensureProto(from)
|
|
|
47
52
|
}
|
|
48
53
|
else
|
|
49
54
|
{
|
|
50
|
-
throw new TypeError("Invalid class constructor or object
|
|
55
|
+
throw new TypeError("Invalid class constructor or instance object");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* For when we need a constructor function exclusively.
|
|
61
|
+
*
|
|
62
|
+
* @param {(function|object)} from - Target
|
|
63
|
+
*
|
|
64
|
+
* May either be a class constructor `function`, or an `object` instance.
|
|
65
|
+
*
|
|
66
|
+
* @returns {function}
|
|
67
|
+
*
|
|
68
|
+
* If `from` is a constructor function it will be returned as-is.
|
|
69
|
+
* If `from` is an object instance (with a constructor other than `Object`),
|
|
70
|
+
* then the `from.constructor` value will be returned.
|
|
71
|
+
*
|
|
72
|
+
* @throws {TypeError} If `from` was not a valid value;
|
|
73
|
+
* including if it was a function without a prototype,
|
|
74
|
+
* or an object with a constructor of `Object`.
|
|
75
|
+
*
|
|
76
|
+
* @alias module:@lumjs/core/traits.ensureConstructor
|
|
77
|
+
*/
|
|
78
|
+
function ensureConstructor(from)
|
|
79
|
+
{
|
|
80
|
+
if (isConstructor(from))
|
|
81
|
+
{ // Good to go
|
|
82
|
+
return from;
|
|
83
|
+
}
|
|
84
|
+
else if (isClassObject(from))
|
|
85
|
+
{ // An instance of a class (other than `Object` itself)
|
|
86
|
+
return from.constructor;
|
|
87
|
+
}
|
|
88
|
+
else
|
|
89
|
+
{
|
|
90
|
+
throw new TypeError("Invalid class constructor or instance object");
|
|
51
91
|
}
|
|
52
92
|
}
|
|
53
93
|
|
|
@@ -125,13 +165,16 @@ function getComposed(target, source, tryClass=true)
|
|
|
125
165
|
composed = target[COMPOSED_TRAITS];
|
|
126
166
|
}
|
|
127
167
|
|
|
128
|
-
if (composed
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
168
|
+
if (composed)
|
|
169
|
+
{
|
|
170
|
+
if (!source)
|
|
171
|
+
{
|
|
172
|
+
return composed;
|
|
173
|
+
}
|
|
174
|
+
if (composed.has(source))
|
|
175
|
+
{ // Let's get definitions for a specific trait.
|
|
176
|
+
return composed.get(source);
|
|
177
|
+
}
|
|
135
178
|
}
|
|
136
179
|
|
|
137
180
|
if (tryClass && isObj(target))
|
|
@@ -224,9 +267,27 @@ function mapComposed(target, source)
|
|
|
224
267
|
* or use the {@link module:@lumjs/core/traits.composeFully} function.
|
|
225
268
|
*
|
|
226
269
|
* Only useful if `source` is a class constructor (always recommended).
|
|
270
|
+
* The exact behaviour may be customized further with the `.flags` option.
|
|
227
271
|
*
|
|
228
272
|
* Default is `false`.
|
|
229
273
|
*
|
|
274
|
+
* @param {number} [opts.flags] Binary flags for advanced behaviours
|
|
275
|
+
*
|
|
276
|
+
* The default `.flags` value, and the _ensure function_ used depends
|
|
277
|
+
* on the value of the `.static` option:
|
|
278
|
+
*
|
|
279
|
+
* | .static | Default | Ensure function |
|
|
280
|
+
* | ------- | ------- | --------------------------------------------------- |
|
|
281
|
+
* | `false` | `3` | {@link module:@lumjs/core/traits.ensureProto} |
|
|
282
|
+
* | `true` | `0` | {@link module:@lumjs/core/traits.ensureConstructor} |
|
|
283
|
+
*
|
|
284
|
+
* The actual flags are fairly straightforward:
|
|
285
|
+
*
|
|
286
|
+
* | Flag | Description |
|
|
287
|
+
* | ---- | ---------------------------------------------------------------- |
|
|
288
|
+
* | `1` | Pass `target` through _ensure function_ |
|
|
289
|
+
* | `2` | Pass `source` through _ensure function_ |
|
|
290
|
+
*
|
|
230
291
|
* @param {boolean} [opts.symbols=false] Auto-compose `Symbol` properties?
|
|
231
292
|
*
|
|
232
293
|
* Calls `Object.getOwnPropertySymbols()` when generating a list of
|
|
@@ -260,8 +321,7 @@ function compose(specTarget, specSource, opts={})
|
|
|
260
321
|
= getComposed(specTarget, specSource)
|
|
261
322
|
?? mapComposed(specTarget, specSource);
|
|
262
323
|
|
|
263
|
-
const isStatic = opts.static ?? false;
|
|
264
|
-
|
|
324
|
+
const isStatic = opts.static ?? false;
|
|
265
325
|
const mapId = isStatic ? 'static' : 'proto';
|
|
266
326
|
|
|
267
327
|
if (composedMaps[mapId])
|
|
@@ -270,8 +330,11 @@ function compose(specTarget, specSource, opts={})
|
|
|
270
330
|
return composedMaps;
|
|
271
331
|
}
|
|
272
332
|
|
|
273
|
-
const
|
|
274
|
-
const
|
|
333
|
+
const flags = parseInt(opts.flags) ?? (isStatic ? FLAGS_S : FLAGS_O);
|
|
334
|
+
const ensure = isStatic ? ensureConstructor : ensureProto;
|
|
335
|
+
|
|
336
|
+
const target = (flags & FLAG_ET) ? specTarget : ensure(specTarget);
|
|
337
|
+
const source = (flags & FLAG_ES) ? specSource : ensure(specSource);
|
|
275
338
|
|
|
276
339
|
let props = opts.props;
|
|
277
340
|
|
|
@@ -382,7 +445,6 @@ function compose(specTarget, specSource, opts={})
|
|
|
382
445
|
composed.set(tprop, cdef);
|
|
383
446
|
}
|
|
384
447
|
|
|
385
|
-
composedMaps[mapId] = composed;
|
|
386
448
|
return composedMaps;
|
|
387
449
|
}
|
|
388
450
|
|
|
@@ -642,9 +704,10 @@ class CoreTrait
|
|
|
642
704
|
* @param {boolean} info.ok - Will always be `true` initially.
|
|
643
705
|
*
|
|
644
706
|
* If an overridden `removeTrait()` method sets this to `false`,
|
|
645
|
-
* then the
|
|
646
|
-
* decomposing the trait.
|
|
707
|
+
* then the decomposeFrom() operation will skip the step of
|
|
708
|
+
* actually decomposing the trait.
|
|
647
709
|
*
|
|
710
|
+
* @returns {*} Return value is not used.
|
|
648
711
|
*/
|
|
649
712
|
static removeTrait(info)
|
|
650
713
|
{
|
|
@@ -714,7 +777,7 @@ class CoreTrait
|
|
|
714
777
|
* @returns {function} The `registerTrait()` function created above.
|
|
715
778
|
* @alias module:@lumjs/core/traits.makeRegistry
|
|
716
779
|
*/
|
|
717
|
-
function makeTraitRegistry(registry)
|
|
780
|
+
function makeTraitRegistry(registry={})
|
|
718
781
|
{
|
|
719
782
|
needObj(registry, false, 'invalid trait registry object');
|
|
720
783
|
|
|
@@ -749,56 +812,10 @@ function makeTraitRegistry(registry)
|
|
|
749
812
|
module.exports =
|
|
750
813
|
{
|
|
751
814
|
compose, composeFully, getComposed, decompose,
|
|
752
|
-
Trait: CoreTrait, IGNORE_STATIC,
|
|
815
|
+
Trait: CoreTrait, IGNORE_STATIC,
|
|
816
|
+
ensureProto, ensureConstructor,
|
|
753
817
|
makeRegistry: makeTraitRegistry,
|
|
754
818
|
|
|
755
819
|
// Undocumented:
|
|
756
820
|
hasOwn,
|
|
757
821
|
}
|
|
758
|
-
|
|
759
|
-
/**
|
|
760
|
-
* Composed property maps.
|
|
761
|
-
*
|
|
762
|
-
* @typedef {object} module:@lumjs/core/traits~Composed
|
|
763
|
-
*
|
|
764
|
-
* @prop {?Map} proto - Map of composed *prototype* properties.
|
|
765
|
-
*
|
|
766
|
-
* Keys are the `string` or `symbol` for each composed property.
|
|
767
|
-
* Values are {@link module:@lumjs/core/traits~ComposedProperty} objects.
|
|
768
|
-
*
|
|
769
|
-
* If no applicable trait properties are currently composed,
|
|
770
|
-
* this will be `null`.
|
|
771
|
-
*
|
|
772
|
-
* @prop {?Map} static - Map of composed *static* properties.
|
|
773
|
-
*
|
|
774
|
-
* Exactly the same description as `proto`, but for static properties.
|
|
775
|
-
*
|
|
776
|
-
* @prop {boolean} forClass - Are these property maps from a class?
|
|
777
|
-
*
|
|
778
|
-
* Will be `true` if the original target was a `function`, or `false`
|
|
779
|
-
* otherwise. Only really useful when trait functions need to be called
|
|
780
|
-
* differently depending on if the trait was applied to an individual
|
|
781
|
-
* instance or a class constructor.
|
|
782
|
-
*
|
|
783
|
-
*/
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* Composed property definitions.
|
|
787
|
-
*
|
|
788
|
-
* @typedef {object} module:@lumjs/core/traits~ComposedProperty
|
|
789
|
-
*
|
|
790
|
-
* @prop {(string|Symbol)} id - The property key on the `target`
|
|
791
|
-
* @prop {(string|Symbol)} [srcKey] The property key from the `source`;
|
|
792
|
-
* only included if different from `propKey`.
|
|
793
|
-
*
|
|
794
|
-
* @prop {?object} newDescriptor - The property descriptor to be added;
|
|
795
|
-
* will be `null` if the property did not exist.
|
|
796
|
-
*
|
|
797
|
-
* @prop {object} [oldDescriptor] The replaced property descriptor;
|
|
798
|
-
* only included if there was an existing property in the `target`
|
|
799
|
-
* and the `opts.overwrite` option was `true`.
|
|
800
|
-
*
|
|
801
|
-
* @prop {boolean} found - Was the property found in the `source` ?
|
|
802
|
-
* @prop {boolean} added - Was the property added to the `target` ?
|
|
803
|
-
*
|
|
804
|
-
*/
|
package/lib/types/basics.js
CHANGED
|
@@ -144,6 +144,9 @@ function isIterable(v)
|
|
|
144
144
|
/**
|
|
145
145
|
* Does a value appear to be a class constructor?
|
|
146
146
|
*
|
|
147
|
+
* For the purposes of this test, a class constructor is
|
|
148
|
+
* any Function that has a `prototype` Object property.
|
|
149
|
+
*
|
|
147
150
|
* @param {*} v - Value to test
|
|
148
151
|
* @returns {boolean}
|
|
149
152
|
* @alias module:@lumjs/core/types/basics.isConstructor
|
|
@@ -153,6 +156,35 @@ function isConstructor(v)
|
|
|
153
156
|
return (typeof v === F && isObj(v.prototype));
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Is a value a _plain_ object?
|
|
161
|
+
*
|
|
162
|
+
* A plain object is one which has a `constructor` property
|
|
163
|
+
* that **IS** the `globalThis.Object` function.
|
|
164
|
+
*
|
|
165
|
+
* @param {*} v - Value to test
|
|
166
|
+
* @returns {boolean}
|
|
167
|
+
*/
|
|
168
|
+
function isPlainObject(v)
|
|
169
|
+
{
|
|
170
|
+
return (isObj(v) && v.constructor === Object);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Is a value a class instance object?
|
|
175
|
+
*
|
|
176
|
+
* This is a counterpart to isPlainObject() that only returns
|
|
177
|
+
* true if the `constructor` property exists, but is **NOT**
|
|
178
|
+
* the `globalThis.Object` function.
|
|
179
|
+
*
|
|
180
|
+
* @param {*} v - Value to test
|
|
181
|
+
* @returns {boolean}
|
|
182
|
+
*/
|
|
183
|
+
function isClassObject(v)
|
|
184
|
+
{
|
|
185
|
+
return (isObj(v) && typeof v.constructor === F && v.constructor !== Object);
|
|
186
|
+
}
|
|
187
|
+
|
|
156
188
|
/**
|
|
157
189
|
* See if an object can be used as a valid *descriptor*.
|
|
158
190
|
*
|
|
@@ -246,7 +278,8 @@ module.exports =
|
|
|
246
278
|
{
|
|
247
279
|
isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
|
|
248
280
|
isIterable, nonEmptyArray, isArguments, isProperty, isConstructor,
|
|
249
|
-
doesDescriptor, doesDescriptorTemplate,
|
|
281
|
+
isPlainObject, isClassObject, doesDescriptor, doesDescriptorTemplate,
|
|
282
|
+
JS,
|
|
250
283
|
}
|
|
251
284
|
|
|
252
285
|
JS.addTo(module.exports);
|