@lumjs/core 1.15.0 → 1.18.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/observable.js CHANGED
@@ -1,4 +1,4 @@
1
-
1
+ // Defining these after observable.
2
2
  const {B,F,S,def,isObj,isComplex,TYPES,console} = require('./types');
3
3
  const {duplicateAll: clone} = require('./obj/copyall');
4
4
  const lock = Object.freeze;
@@ -11,31 +11,46 @@ const lock = Object.freeze;
11
11
  * @param {object} el - The object we are making observable.
12
12
  * @param {object} [opts] Options that define behaviours.
13
13
  * @param {string} [opts.wildcard='*'] The event name used as a wildcard.
14
- * @param {boolean} [opts.wrapthis=false] If `true`, `this` will be a wrapper.
15
14
  *
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:
18
- *
15
+ * @param {boolean} [opts.wrapargs=false] If `true`, the event handlers will
16
+ * be passed a wrapper object as the sole argument:
17
+ *
19
18
  * ```js
20
- * {
19
+ * {
21
20
  * isObservable:
22
- * {
21
+ * { // Immutable;
23
22
  * event: true, // This is an event data object.
24
23
  * target: false, // This is not the target object.
25
24
  * },
26
- * self: el, // The target object.
27
- * name: event, // The event name that was triggered.
28
25
  * wildcard: bool, // Will be true if this was a wildcard event handler.
29
26
  * func: function, // The function being called.
30
27
  * args: array, // The arguments passed to trigger.
28
+ * self: el, // The target object.
29
+ * name: event, // The event name that was triggered.
30
+ * target: el, // An alias to `self`.
31
+ * type: event, // An alias to `name`.
32
+ * // ...
31
33
  * }
32
34
  * ```
33
35
  *
34
- * The object will be frozen so its values cannot be modified.
36
+ * @param {boolean} [opts.wrapthis=false] If `true`, `this` in event
37
+ * handlers will be the same object as `opts.wrapargs`.
38
+ *
39
+ * @param {?function} [opts.wrapsetup=null] Setup function for wrapper data.
40
+ *
41
+ * This function will be called with the target `el` as `this`,
42
+ * and will be passed the wrapper object (before it is locked),
43
+ * so it can examine the event name and arguments passed to
44
+ * `trigger()` and adjust the object accordingly.
35
45
  *
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`.
46
+ * This allows the wrapper objects to have a lot more custom properties,
47
+ * and feel more like the standard Event objects used by the `DOM`.
48
+ *
49
+ * If this is specified, but neither `opts.wrapargs` or `opts.wrapthis`
50
+ * is `true`, then `opts.wrapargs` will be changed to `true` implicitly.
51
+ *
52
+ * @param {boolean} [opts.wraplock=true] If `true`, the wrapper object
53
+ * will be made immutable using the `Object.freeze()` method.
39
54
  *
40
55
  * @param {boolean} [opts.addname] If `true` callbacks with
41
56
  * multiple events will have the name of the triggered event added as
@@ -99,16 +114,24 @@ function observable (el={}, opts={})
99
114
  ? opts.wildcard
100
115
  : '*';
101
116
 
117
+ const wrapsetup = (typeof opts.wrapsetup === F)
118
+ ? opts.wrapsetup
119
+ : null;
120
+
102
121
  const wrapthis = (typeof opts.wrapthis === B)
103
122
  ? opts.wrapthis
104
123
  : false;
105
124
 
106
125
  const wrapargs = (typeof opts.wrapargs === B)
107
126
  ? opts.wrapargs
108
- : false;
127
+ : (wrapsetup ? !wrapthis : false);
109
128
 
110
129
  const wrapped = (wrapthis || wrapargs);
111
130
 
131
+ const wraplock = (typeof opts.wraplock === B)
132
+ ? opts.wraplock
133
+ : true;
134
+
112
135
  const addname = (typeof opts.addname === B)
113
136
  ? opts.addname
114
137
  : !wrapped;
@@ -150,19 +173,32 @@ function observable (el={}, opts={})
150
173
 
151
174
  let fobj;
152
175
 
153
- if (wrapthis || wrapargs)
176
+ if (wrapped)
154
177
  { // Something is going to use our wrapper object.
155
178
  const isWild = (name === wildcard);
156
179
  const fname = isWild ? (addname ? args[0] : args.shift()) : name;
180
+
157
181
  fobj =
158
- lock({
182
+ {
159
183
  isObservable: lock({event: true, target: false}),
160
184
  self: el,
161
- name: fname,
185
+ target: el,
186
+ name: fname,
187
+ type: fname,
162
188
  func: fn,
163
189
  wildcard: isWild,
164
190
  args,
165
- });
191
+ };
192
+
193
+ if (wrapsetup)
194
+ {
195
+ wrapsetup.call(el, fobj);
196
+ }
197
+
198
+ if (wraplock)
199
+ {
200
+ lock(fobj);
201
+ }
166
202
  }
167
203
 
168
204
  const fthis = wrapthis ? fobj : el;
package/lib/opt.js CHANGED
@@ -3,12 +3,13 @@
3
3
  * @module @lumjs/core/opt
4
4
  */
5
5
 
6
- const {U,F,S,needObj,needType} = require('./types');
6
+ const {U,F,S,N,B,isObj,isComplex,needObj,needType} = require('./types');
7
+ const {insert} = require('./arrays/add');
7
8
 
8
9
  /**
9
10
  * See if a value is *set*, and if not, return a default value.
10
11
  *
11
- * @param {*} opt - The value we are testing.
12
+ * @param {*} optvalue - The value we are testing.
12
13
  * @param {*} defvalue - The default value if opt was null or undefined.
13
14
  *
14
15
  * @param {boolean} [allowNull=false] If true, allow null to count as *set*.
@@ -17,22 +18,28 @@ const {U,F,S,needObj,needType} = require('./types');
17
18
  * the default.
18
19
  * @param {object} [lazyThis=null] If `isLazy` is true, this object will
19
20
  * be used as `this` for the function.
21
+ * @param {Array} [lazyArgs] If `isLazy` is true, this may be used
22
+ * as a list of arguments to pass.
20
23
  *
21
24
  * @return {*} Either the specified `opt` value or the default value.
22
25
  * @alias module:@lumjs/core/opt.val
23
26
  */
24
- function val(opt, defvalue, allowNull=false, isLazy=false, lazyThis=null)
27
+ function val(optvalue, defvalue,
28
+ allowNull=false,
29
+ isLazy=false,
30
+ lazyThis=null,
31
+ lazyArgs=[])
25
32
  {
26
- if (typeof opt === U || (!allowNull && opt === null))
33
+ if (typeof optvalue === U || (!allowNull && optvalue === null))
27
34
  { // The defined value was not "set" as per our rules.
28
35
  if (isLazy && typeof defvalue === F)
29
36
  { // Get the default value from a passed in function.
30
- return defvalue.call(lazyThis);
37
+ return defvalue.apply(lazyThis, lazyArgs);
31
38
  }
32
39
  return defvalue;
33
40
  }
34
41
 
35
- return opt;
42
+ return optvalue;
36
43
  }
37
44
 
38
45
  exports.val = val;
@@ -48,18 +55,350 @@ exports.val = val;
48
55
  * @param {string} optname - The property name we're checking for.
49
56
  * @param {*} defvalue - The default value.
50
57
  *
51
- * @param {bool} [allowNull=true] Same as `val()`, but the default is `true`.
58
+ * @param {bool} [allowNull=true] Same as `val()`, but default is `true`.
52
59
  * @param {bool} [isLazy=false] Same as `val()`.
53
- * @param {object} [lazyThis=opts] Same as `val()`.
60
+ * @param {object} [lazyThis=opts] Same as `val()`, but default is `obj`.
61
+ * @param {Array} [lazyArgs] Same as `val()`.
54
62
  *
55
63
  * @return {*} Either the property value, or the default value.
56
- * module:@lumjs/core/opt.get
64
+ * @alias module:@lumjs/core/opt.get
57
65
  */
58
- function get(obj, optname, defvalue, allowNull=true, isLazy=false, lazyThis=obj)
66
+ function get(obj, optname, defvalue,
67
+ allowNull=true,
68
+ isLazy=false,
69
+ lazyThis=obj,
70
+ lazyArgs)
59
71
  {
60
72
  needObj(obj);
61
73
  needType(S, optname);
62
- return val(obj[optname], defvalue, allowNull, isLazy, lazyThis);
74
+ return val(obj[optname], defvalue, allowNull, isLazy, lazyThis, lazyArgs);
63
75
  }
64
76
 
65
77
  exports.get = get;
78
+
79
+ /**
80
+ * A class for handling options with multiple sources.
81
+ * @alias module:@lumjs/core/opt.Opts
82
+ */
83
+ class Opts
84
+ {
85
+ /**
86
+ * Build an Opts instance.
87
+ *
88
+ * @param {...object} sources - Initial sources of options.
89
+ *
90
+ * The order of sources matters, as the ones added later will override
91
+ * the ones added earlier. Keep that in mind when adding sources.
92
+ */
93
+ constructor(...sources)
94
+ {
95
+ this.$sources = [];
96
+ this.$curPos = -1;
97
+ this.$curSrc = null;
98
+
99
+ this.$fatalErrors = false;
100
+ this.$strictProps = false;
101
+
102
+ this.add(...sources);
103
+ this._compile();
104
+ }
105
+
106
+ /**
107
+ * Compile current sources into a data object.
108
+ *
109
+ * @returns {object} `this`
110
+ * @private
111
+ */
112
+ _compile()
113
+ {
114
+ this.$data = Object.assign({}, ...this.$sources);
115
+ return this;
116
+ }
117
+
118
+ /**
119
+ * Handle an error
120
+ *
121
+ * @param {string} msg - A summary of the error.
122
+ * @param {object} info - Debugging information for the logs.
123
+ * @param {function} [errClass=TypeError] Constructor for an `Error` class.
124
+ * Used if fatal errors are enabled.
125
+ *
126
+ * @returns {object} `this`
127
+ * @throws {Error} An error of `errClass` class, if fatal mode is enabled.
128
+ */
129
+ _err(msg, info={}, errClass=TypeError)
130
+ {
131
+ const args = this.$fatalErrors ? [info] : [msg, info];
132
+
133
+ info.instance = this;
134
+ console.error(...args);
135
+
136
+ if (this.$fatalErrors)
137
+ {
138
+ throw new errClass(msg);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Set the fatal error handling setting.
144
+ *
145
+ * Default is `false`, so errors will be logged, but not thrown.
146
+ *
147
+ * @param {boolean} val - Should errors be fatal?
148
+ * @returns {object} `this`
149
+ */
150
+ fatal(val)
151
+ {
152
+ if (typeof val === B)
153
+ {
154
+ this.$fatalErrors = val;
155
+ }
156
+ else
157
+ {
158
+ this._err('invalid fatal value', {val});
159
+ }
160
+
161
+ return this;
162
+ }
163
+
164
+ /**
165
+ * Set the strict property check setting.
166
+ *
167
+ * Default is `false`, we don't care about non-existent properties.
168
+ *
169
+ * @param {boolean} val - Should non-existant properties be an error?
170
+ * @returns {object} `this`
171
+ */
172
+ strict(val)
173
+ {
174
+ if (typeof val === B)
175
+ {
176
+ this.$strictProps = val;
177
+ }
178
+ else
179
+ {
180
+ this._err('invalid strict value', {val});
181
+ }
182
+
183
+ return this;
184
+ }
185
+
186
+ /**
187
+ * Set the position/offset to add new sources at.
188
+ *
189
+ * This will affect subsequent calls to the `add()` method.
190
+ *
191
+ * @param {number} pos - The position/offset value.
192
+ *
193
+ * - A value of `-1` uses `Array#push(src))`; end of array.
194
+ * - A value of `0` uses `Array#unshift(src)`; start of array.
195
+ * - Any value `> 0` uses `Array#splice(pos, 0, src)`; offset from start.
196
+ * - Any value `< -1` uses `Array#splice(pos+1, 0, src)`; offset from end.
197
+ *
198
+ * The default value if none is specified is `-1`.
199
+ *
200
+ * @returns {object} `this`
201
+ * @throws {TypeError} An invalid value was passed while `fatal` was true.
202
+ */
203
+ at(pos)
204
+ {
205
+ if (typeof pos === N)
206
+ {
207
+ this.$curPos = pos;
208
+ }
209
+ else
210
+ {
211
+ this._err("Invalid pos value", {pos});
212
+ }
213
+
214
+ return this;
215
+ }
216
+
217
+ /**
218
+ * Set the object to look for nested properties in.
219
+ *
220
+ * This will affect subsequent calls to the `add()` method.
221
+ *
222
+ * @param {(object|number|boolean)} source - Source definition
223
+ *
224
+ * - If this is an `object` it will be used as the object directly.
225
+ * - If this is a `number` it is the position of one of our data sources.
226
+ * Negative numbers count from the end of the list of sources.
227
+ * - If this is `true` then the compiled options data at the time of the
228
+ * call to this method will be used.
229
+ * - If this is `false` then the next time a `string` value is passed to
230
+ * `add()` the options will be compiled on demand, and that object will
231
+ * be used until the next call to `from()`.
232
+ *
233
+ * If this is not specified, then it defaults to `false`.
234
+ *
235
+ * @returns {object} `this`
236
+ * @throws {TypeError} An invalid value was passed while `fatal` was true.
237
+ */
238
+ from(source)
239
+ {
240
+ if (source === true)
241
+ { // Use existing data as the source.
242
+ this.$curSrc = this.$data;
243
+ }
244
+ else if (source === false)
245
+ { // Auto-generate the source the next time.
246
+ this.$curSrc = null;
247
+ }
248
+ else if (typeof source === N)
249
+ { // A number will be the position of an existing source.
250
+ const offset
251
+ = (source < 0)
252
+ ? this.$sources.length + source
253
+ : source;
254
+
255
+ if (isObj(this.$sources[offset]))
256
+ {
257
+ this.$curSrc = this.$sources[offset];
258
+ }
259
+ else
260
+ {
261
+ this._err("Invalid source offset", {offset, source});
262
+ }
263
+ }
264
+ else if (isObj(source))
265
+ { // An object or function will be used as the source.
266
+ this.$curSrc = source;
267
+ }
268
+ else
269
+ {
270
+ this._err("Invalid source", {source});
271
+ }
272
+
273
+ return this;
274
+ }
275
+
276
+ /**
277
+ * Add new sources of options.
278
+ *
279
+ * @param {...(object|string)} sources - Sources and positions.
280
+ *
281
+ * If this is an `object` then it's a source of options to add.
282
+ * This is the most common way of using this.
283
+ *
284
+ * If this is a `string` then it's assumed to be nested property
285
+ * of the current `from()` source, and if that property exists and
286
+ * is an object, it will be used as the source to add. If it does
287
+ * not exist, then the behaviour will depend on the values of the
288
+ * `strict()` and `fatal()` modifiers.
289
+ *
290
+ * @returns {object} `this`
291
+ */
292
+ add(...sources)
293
+ {
294
+ let pos=-1;
295
+
296
+ for (let source of sources)
297
+ {
298
+ if (source === undefined || source === null)
299
+ { // Skip undefined or null values.
300
+ continue;
301
+ }
302
+
303
+ if (typeof source === S)
304
+ { // Try to find a nested property to include.
305
+ if (this.$curSrc === null)
306
+ { // Has not been initialized, let's do that now.
307
+ this._compile();
308
+ this.$curSrc = this.$data;
309
+ }
310
+
311
+ if (isObj(this.$curSrc[source]))
312
+ { // Found a property, use it.
313
+ source = this.$curSrc[source];
314
+ }
315
+ else
316
+ { // No such property.
317
+ if (this.$strictProps)
318
+ {
319
+ this._err('Property not found', {source});
320
+ }
321
+ continue;
322
+ }
323
+ }
324
+
325
+ if (isObj(source))
326
+ { // It's a source to add.
327
+ insert(this.$sources, source, pos);
328
+ }
329
+ else
330
+ { // That's not valid.
331
+ this._err('invalid source value', {source, sources});
332
+ }
333
+ }
334
+
335
+ return this._compile();
336
+ }
337
+
338
+ /**
339
+ * Remove existing sources of options.
340
+ *
341
+ * @param {...object} sources - Sources to remove.
342
+ *
343
+ * @returns {object} `this`
344
+ */
345
+ remove(...sources)
346
+ {
347
+ for (const source of sources)
348
+ {
349
+ const index = this.$sources.indexOf(source);
350
+ if (index !== -1)
351
+ {
352
+ this.$sources.splice(index, 1);
353
+ }
354
+ }
355
+
356
+ return this._compile();
357
+ }
358
+
359
+ /**
360
+ * Remove all current sources. Resets compiled options data.
361
+ *
362
+ * @returns {object} `this`
363
+ */
364
+ clear()
365
+ {
366
+ this.$sources = [];
367
+ return this._compile();
368
+ }
369
+
370
+ /**
371
+ * Get an option value from our compiled data sources.
372
+ *
373
+ * This uses the `get()` function, but instead of using positional
374
+ * arguments, it supports an object of named options instead.
375
+ *
376
+ * @param {string} opt - The name of the option to get.
377
+ * @param {object} [args] Optional arguments for `get()`.
378
+ * @param {*} [args.default] `defvalue` argument.
379
+ * @param {boolean} [args.null=true] `allowNull` argument.
380
+ * @param {boolean} [args.lazy=false] `isLazy` argument.
381
+ * @param {(object|function)} [args.lazyThis] `lazyThis` argument.
382
+ * If this is defined, `args.lazy` will be set to `true`.
383
+ * @param {Array} [args.lazyArgs] `lazyArgs` argument.
384
+ * If this is defined, `args.lazy` will be set to `true`.
385
+ *
386
+ * @returns {*} The output of the `get()` function.
387
+ */
388
+ get(opt, args={})
389
+ {
390
+ if (isComplex(args.lazyThis) || Array.isArray(args.lazyArgs))
391
+ {
392
+ args.lazy = true;
393
+ }
394
+
395
+ return get(this.$data, opt,
396
+ args.default,
397
+ args.null,
398
+ args.lazy,
399
+ args.lazyThis,
400
+ args.lazyArgs);
401
+ }
402
+ }
403
+
404
+ exports.Opts = Opts;
package/lib/strings.js CHANGED
@@ -189,3 +189,31 @@ function replaceItems(string, replacements, useAll)
189
189
  }
190
190
 
191
191
  exports.replaceItems = replaceItems;
192
+
193
+ /**
194
+ * Replace values in a string via an async function.
195
+ *
196
+ * @param {string} string - The input string.
197
+ *
198
+ * @param {(RegExp|string)} regexp - The pattern to find.
199
+ *
200
+ * If this is a `RegExp`, it *must* have the `g` flag set.
201
+ * If this is a `string` it will be converted into a `RegExp`,
202
+ * see the `String#matchAll` method for details.
203
+ *
204
+ * @param {function} replacerFunction - An `async` function.
205
+ *
206
+ * See `String#replace` for details on replacer functions.
207
+ *
208
+ * @returns {string}
209
+ */
210
+ async function replaceAsync(string, regexp, replacerFunction)
211
+ {
212
+ const replacements = await Promise.all(
213
+ Array.from(string.matchAll(regexp),
214
+ match => replacerFunction(...match)));
215
+ let i = 0;
216
+ return string.replace(regexp, () => replacements[i++]);
217
+ }
218
+
219
+ exports.replaceAsync = replaceAsync;