@lumjs/core 1.10.0 → 1.12.0

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/enum.js CHANGED
@@ -9,9 +9,62 @@ const ENUM_ID = new InternalObjectId({name: '$Enum'});
9
9
  *
10
10
  * Like the built-in `Symbol`, this is called as a function, not a constructor.
11
11
  *
12
- * @param {*} spec - TBD
13
- * @param {*} [opts] - TBD
12
+ * @param {(string[]|object)} spec - May be in one of two formats:
13
+ *
14
+ * - An `Array` of strings. Each string represents the name of an Enum
15
+ * property name. The property value will be automatically determined
16
+ * based on the options. The default is a series of sequential integers
17
+ * starting at `0`.
18
+ *
19
+ * - A plain object used as a map of property names to underlying values.
20
+ * In most cases the value can be whatever you want and will be directly
21
+ * mapped as is. However if `opts.symbols` is `true`, and the value is a
22
+ * string, the string will be used as the name for the symbol rather than
23
+ * the property name.
24
+ *
25
+ * @param {object} [opts] Options for creating the Enum
26
+ *
27
+ * @param {bool} [opts.symbols=false] Use `Symbol` property values.
28
+ *
29
+ * @param {bool} [opts.globals=false] Use `Symbol.for()` property values.
30
+ *
31
+ * This option is only used if `opts.symbols` is `true`.
32
+ *
33
+ * @param {bool} [opts.strings=false] The property name is also the value.
34
+ *
35
+ * This is only used if `spec` is an `Array`, and `opts.symbols` is `false`.
36
+ *
37
+ * @param {bool} [opts.flags=false] Use binary flag property values.
38
+ *
39
+ * This is only used if `spec` is an `Array`, and both `opts.symbols` and
40
+ * `opts.strings` are `false`.
41
+ *
42
+ * This handles incrementing automatically, so: `[1,2,4,8,16,...]`
43
+ *
44
+ * @param {number} [opts.counter] The starting value when auto-incrementing.
45
+ *
46
+ * The default value is `1` if `opts.flags` is `true` or `0` otherwise.
47
+ *
48
+ * @param {bool} [opts.open=false] If `true` the Enum object won't be locked.
49
+ * If `false`, by default we use `Object.freeze()` to lock the object.
50
+ *
51
+ * @param {(bool|object|Array)} [opts.lock=null] If this is *not* `null`,
52
+ * use {@see module:@lumjs/core/obj.lock lock()} instead of `Object.freeze()`.
53
+ *
54
+ * Only used if `opts.open` is `false`. The supported values are:
55
+ *
56
+ * - `Array`: The `[cloneable, cloneOpts, useSeal]` parameters for `clone()`.
57
+ * - `object`: The `cloneOpts` parameter for `clone()`.
58
+ * - `bool`: The `cloneable` parameter for `clone()`.
59
+ *
60
+ * @param {bool} [opts.configurable=false] Enum properties are configurable?
61
+ *
62
+ * This option is ignored if `opts.open` is `false`.
63
+ *
64
+ * @param {bool} [opts.enumerable=true] Enum properties are enumerable?
65
+ *
14
66
  * @returns {object} A magic Enum object.
67
+ * @throws {TypeError} If an invalid value was passed.
15
68
  * @exports module:@lumjs/core/enum
16
69
  */
17
70
  function Enum (spec, opts={})
@@ -52,11 +105,10 @@ function Enum (spec, opts={})
52
105
 
53
106
  if (Array.isArray(spec))
54
107
  { // An array of strings is expected.
55
- let counter = opts.counter ?? 1;
108
+ let counter = opts.counter ?? (opts.flags ? 1 : 0);
56
109
 
57
- for (let i = 0; i < spec.length; i++)
110
+ for (const name of spec)
58
111
  {
59
- const name = spec[i];
60
112
  if (typeof name !== S)
61
113
  {
62
114
  throw new TypeError("Non-string passed in Enum object");
@@ -65,7 +117,7 @@ function Enum (spec, opts={})
65
117
  const val
66
118
  = opts.strings
67
119
  ? name
68
- : (opts.flags ? counter : i);
120
+ : counter;
69
121
 
70
122
  addVal(name, name, val);
71
123
 
@@ -73,6 +125,10 @@ function Enum (spec, opts={})
73
125
  { // Increment the binary flag counter.
74
126
  counter *= 2;
75
127
  }
128
+ else
129
+ { // Increment a regular counter.
130
+ counter++;
131
+ }
76
132
  }
77
133
  }
78
134
  else
@@ -85,30 +141,33 @@ function Enum (spec, opts={})
85
141
  }
86
142
  }
87
143
 
88
- if (notNil(opts.lock))
89
- { // Use lock() function.
90
- let lockOpts;
91
- if (Array.isArray(opts.lock))
92
- { // Specified the lock parameters as an array.
93
- lockOpts = opts.lock;
94
- }
95
- else if (isObj(opts.lock))
96
- { // An object is assumed to be the `cloneOpts` parameter.
97
- lockOpts = [true, opts.lock, false];
98
- }
99
- else if (typeof opts.lock === B)
100
- { // Boolean is assumed to be the `cloneable` parameter.
101
- lockOpts = [opts.lock, null, false];
144
+ if (!opts.open)
145
+ {
146
+ if (notNil(opts.lock))
147
+ { // Use lock() function.
148
+ let lockOpts;
149
+ if (Array.isArray(opts.lock))
150
+ { // Specified the lock parameters as an array.
151
+ lockOpts = opts.lock;
152
+ }
153
+ else if (isObj(opts.lock))
154
+ { // An object is assumed to be the `cloneOpts` parameter.
155
+ lockOpts = [true, opts.lock, false];
156
+ }
157
+ else if (typeof opts.lock === B)
158
+ { // Boolean is assumed to be the `cloneable` parameter.
159
+ lockOpts = [opts.lock, null, false];
160
+ }
161
+ else
162
+ { // Anything else is invalid.
163
+ throw new TypeError('opts.lock was not a valid value');
164
+ }
165
+ return lock(anEnum, ...lockOpts);
102
166
  }
103
- else
104
- { // Anything else we use default values.
105
- lockOpts = [];
167
+ else
168
+ { // Use Object.freeze()
169
+ return Object.freeze(anEnum);
106
170
  }
107
- return lock(anEnum, ...lockOpts);
108
- }
109
- else if (!opts.open)
110
- { // Use Object.freeze()
111
- return Object.freeze(anEnum);
112
171
  }
113
172
 
114
173
  return anEnum;
package/lib/observable.js CHANGED
@@ -1,5 +1,7 @@
1
1
 
2
2
  const {B,F,S,def,isObj,isComplex,TYPES,console} = require('./types');
3
+ const {duplicateAll: clone} = require('./obj/copyall');
4
+ const lock = Object.freeze;
3
5
 
4
6
  /**
5
7
  * Make an object support the *Observable* API.
@@ -11,29 +13,57 @@ const {B,F,S,def,isObj,isComplex,TYPES,console} = require('./types');
11
13
  * @param {string} [opts.wildcard='*'] The event name used as a wildcard.
12
14
  * @param {boolean} [opts.wrapthis=false] If `true`, `this` will be a wrapper.
13
15
  *
14
- * If 'wrapthis' is true, the function will be called with a wrapper object as
15
- * the 'this' variable instead of the target object. The wrapper will be:
16
+ * If `wrapthis` is `true`, the function will be called with a wrapper object
17
+ * as the `this` variable instead of the target object. The wrapper will be:
16
18
  *
17
19
  * ```js
18
20
  * {
21
+ * isObservable:
22
+ * {
23
+ * event: true, // This is an event data object.
24
+ * target: false, // This is not the target object.
25
+ * },
19
26
  * self: el, // The target object.
20
27
  * name: event, // The event name that was triggered.
21
28
  * wildcard: bool, // Will be true if this was a wildcard event handler.
22
29
  * func: function, // The function being called.
30
+ * args: array, // The arguments passed to trigger.
23
31
  * }
24
32
  * ```
25
33
  *
26
- * @param {boolean} [opts.addname=!opts.wrapthis] If `true` callbacks with
27
- * multiple events will have the name of the triggered event added as
28
- * the first parameter.
34
+ * The object will be frozen so its values cannot be modified.
35
+ *
36
+ * @param {boolean} [opts.wrapargs=false] If `true`, the functions will be
37
+ * passed a single object as the sole argument. The object will be the same
38
+ * as the one from `opts.wrapthis`.
39
+ *
40
+ * @param {boolean} [opts.addname] If `true` callbacks with
41
+ * multiple events will have the name of the triggered event added as
42
+ * the first parameter.
43
+ *
44
+ * If either `wrapthis` or `wrapargs` are `true`, then this will default
45
+ * to `false`, otherwise it will default to `true`.
46
+ *
47
+ * @param {boolean} [opts.addis] If `true` add a read-only, frozen
48
+ * object property named `isObservable` with the value:
49
+ *
50
+ * ```js
51
+ * {
52
+ * event: false, // This is not an event data object.
53
+ * target: true, // This is the target object.
54
+ * }
55
+ * ```
56
+ *
57
+ * If either `wrapthis` or `wrapargs` are `true`, then this will default
58
+ * to `true`, otherwise it will default to `false`.
29
59
  *
30
- * @param {boolean} [opts.addis=opts.wrapthis] If `true` add immutable
31
- * property named `isObservable` which will have a value of `true`.
32
60
  * @param {string} [opts.addme] If set, add a method with this name
33
- * to the object, which is a version of `observable()` with the
34
- * default options being the same as the current `opts`.
35
- * If `opts.wrapthis` is `true`, then this defaults to `'makeObservable'`.
36
- * In any other case it defaults to `null`.
61
+ * to the `el` object, which is a version of `observable()` with the
62
+ * default options being the same as the current `opts`.
63
+ *
64
+ * @param {string} [opts.addre] If set, add a method with this name
65
+ * to the `el` object, which is a function that can re-build the
66
+ * observable API methods with new `opts` replacing the old ones.
37
67
  *
38
68
  * @returns {object} el
39
69
  *
@@ -69,30 +99,46 @@ function observable (el={}, opts={})
69
99
  ? opts.wildcard
70
100
  : '*';
71
101
 
72
- const wrapthis = (typeof opts.wrapthis === 'boolean')
102
+ const wrapthis = (typeof opts.wrapthis === B)
73
103
  ? opts.wrapthis
74
104
  : false;
75
105
 
76
- const addname = (typeof opts.addname === 'boolean')
106
+ const wrapargs = (typeof opts.wrapargs === B)
107
+ ? opts.wrapargs
108
+ : false;
109
+
110
+ const wrapped = (wrapthis || wrapargs);
111
+
112
+ const addname = (typeof opts.addname === B)
77
113
  ? opts.addname
78
- : !wrapthis;
114
+ : !wrapped;
79
115
 
80
- const addis = (typeof opts.addis === 'boolean')
116
+ const addis = (typeof opts.addis === B)
81
117
  ? opts.addis
82
- : wrapthis;
118
+ : wrapped;
83
119
 
84
120
  const validIdent = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
85
121
 
86
- const addme = (typeof opts.addme === 'string'
122
+ const addme = (typeof opts.addme === S
87
123
  && validIdent.test(opts.addme))
88
124
  ? opts.addme
89
- : (wrapthis ? 'makeObservable' : null);
125
+ : null;
126
+
127
+ const addre = (typeof opts.addre === S
128
+ && validIdent.test(opts.addre))
129
+ ? opts.addre
130
+ : null;
90
131
 
91
132
  const slice = Array.prototype.slice;
92
133
 
93
134
  function onEachEvent (e, fn)
94
135
  {
95
- e.replace(/\S+/g, fn);
136
+ const es = e.split(/\s+/);
137
+ const me = es.length > 1;
138
+ for (e of es)
139
+ {
140
+ fn(e, me);
141
+ }
96
142
  }
97
143
 
98
144
  const add = def(el);
@@ -102,29 +148,28 @@ function observable (el={}, opts={})
102
148
  if (fn.busy) return;
103
149
  fn.busy = 1;
104
150
 
105
- let fthis;
151
+ let fobj;
106
152
 
107
- if (wrapthis)
108
- {
153
+ if (wrapthis || wrapargs)
154
+ { // Something is going to use our wrapper object.
109
155
  const isWild = (name === wildcard);
110
156
  const fname = isWild ? (addname ? args[0] : args.shift()) : name;
111
- fthis =
112
- {
157
+ fobj =
158
+ lock({
159
+ isObservable: lock({event: true, target: false}),
113
160
  self: el,
114
161
  name: fname,
115
162
  func: fn,
116
163
  wildcard: isWild,
117
- };
118
- }
119
- else
120
- {
121
- fthis = el;
164
+ args,
165
+ });
122
166
  }
123
167
 
124
- let fargs = (fn.typed && addname) ? [name].concat(args) : args;
125
-
168
+ const fthis = wrapthis ? fobj : el;
169
+ const fargs = wrapargs ? [fobj]
170
+ : ((fn.typed && addname) ? [name].concat(args) : args);
171
+
126
172
  fn.apply(fthis, fargs);
127
-
128
173
  fn.busy = 0;
129
174
  }
130
175
 
@@ -147,10 +192,10 @@ function observable (el={}, opts={})
147
192
  return el;
148
193
  }
149
194
 
150
- onEachEvent(events, function(name, pos)
195
+ onEachEvent(events, function(name, typed)
151
196
  {
152
197
  (callbacks[name] = callbacks[name] || []).push(fn);
153
- fn.typed = pos > 0;
198
+ fn.typed = typed;
154
199
  });
155
200
 
156
201
  return el;
@@ -244,28 +289,28 @@ function observable (el={}, opts={})
244
289
 
245
290
  if (addis)
246
291
  {
247
- add('isObservable', true);
292
+ add('isObservable', lock({event: false, target: true}));
248
293
  }
249
294
 
250
295
  if (addme)
251
296
  { // Add a wrapper for observable() that sets new default options.
252
- const ourProps = Object.keys(opts);
253
297
  add(addme, function (obj=null, mopts={})
254
298
  {
255
- if (ourProps.length > 0)
256
- {
257
- for (const prop of ourProps)
258
- {
259
- if (mopts[prop] === undefined)
260
- {
261
- mopts[prop] = opts[prop];
262
- }
263
- }
264
- }
265
- return observable(obj, mopts);
299
+ return observable(obj, clone(opts, mopts));
300
+ });
301
+ }
302
+
303
+ if (addre)
304
+ { // Add a method to change the observable options.
305
+ add(addre, function(opts={})
306
+ {
307
+ return observable(el, opts);
266
308
  });
267
309
  }
268
310
 
311
+ // Metadata
312
+ add('$$observable$$', lock({opts, observable}));
313
+
269
314
  return el
270
315
 
271
316
  } // observable()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {