@lumjs/core 1.26.0 → 1.31.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/README.md CHANGED
@@ -6,6 +6,16 @@ and work in CommonJS/Node.js and modern browsers without any modifications.
6
6
 
7
7
  Used by all the rest of my *Lum.js* libraries.
8
8
 
9
+ ## Notes
10
+
11
+ As of version `1.30.0` the `opt.Opts` class has been split into its the new
12
+ [@lumjs/opts] package, which itself depends on a new [@lumjs/errors] package.
13
+
14
+ Until the next major release (`2.0`), this package will have
15
+ a dependency on the split-away pacakages, so that it can have a deprecated
16
+ alias to the old class name available. As of `2.0` that alias will be
17
+ removed, and the dependencies will also be removed.
18
+
9
19
  ## Documentation
10
20
 
11
21
  ### [API Docs](https://supernovus.github.io/docs/js/@lumjs/core/)
@@ -16,7 +26,7 @@ You can compile the documentation using `npm run build-docs`
16
26
  which will put the generated docs into the `./docs/api` folder.
17
27
 
18
28
  ### [Changelog](./docs/changelogs/1.x.md)
19
- ### [TODO](TODO.md)
29
+ ### [TODO](./docs/TODO.md)
20
30
  ### [Homepage](https://supernovus.github.io/)
21
31
 
22
32
  ## Official URLs
@@ -34,3 +44,7 @@ Timothy Totten <2010@totten.ca>
34
44
 
35
45
  [MIT](https://spdx.org/licenses/MIT.html)
36
46
 
47
+ ---
48
+
49
+ [@lumjs/opts]: https://github.com/supernovus/lum.opts.js
50
+ [@lumjs/errors]: https://github.com/supernovus/lum.errors.js
package/jsdoc.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ const docRules = require('@lumjs/build/jsdoc-rules');
4
+ const ourRules = docRules.docsReadme.srcDocs.clone();
5
+ module.exports = ourRules;
6
+
7
+ /*console.debug(
8
+ {
9
+ docRules,
10
+ ourRules,
11
+ incPath: ourRules?.source?.include,
12
+ });*/
13
+
package/lib/console.js CHANGED
@@ -177,4 +177,58 @@ for (const method of DEFAULT_METHODS)
177
177
  addMethod(method);
178
178
  }
179
179
 
180
+ /**
181
+ * Export our wrapper to replace the real console
182
+ * in the global namespace.
183
+ *
184
+ * @param {*} [handler] Set a handler while we're exported.
185
+ *
186
+ * See the description of `handler` for possible values.
187
+ * Any existing handler value will be saved to the `preHandler`
188
+ * static property.
189
+ *
190
+ * @function module:@lumjs/core/console.export
191
+ */
192
+ def(LC, 'export', function(handler)
193
+ {
194
+ if (handler !== undefined)
195
+ { // Save the existing handler as 'preHandler'
196
+ LC.preHandler = LC.handler ?? null; // Prevent `undefined`
197
+ LC.handler = handler;
198
+ }
199
+
200
+ globalThis.console = LC;
201
+ return LC;
202
+ });
203
+
204
+ /**
205
+ * Restore the real console.
206
+ *
207
+ * If a `handler` was passed to `export()`, then
208
+ * it will be removed, and the previous handler value
209
+ * will be restored.
210
+ *
211
+ * @function module:@lumjs/core/console.restore
212
+ */
213
+ def(LC, 'restore', function()
214
+ {
215
+ if (LC.preHandler !== undefined)
216
+ {
217
+ LC.handler = LC.preHandler;
218
+ delete LC.preHandler;
219
+ }
220
+
221
+ globalThis.console = LC.real;
222
+ return LC;
223
+ });
224
+
225
+ /**
226
+ * A shortcut for `LC.export(false)`
227
+ * @function module:@lumjs/core/console.mute
228
+ */
229
+ def(LC, 'mute', function()
230
+ {
231
+ return LC.export(false);
232
+ });
233
+
180
234
  module.exports = LC;
package/lib/enum.js CHANGED
@@ -1,14 +1,24 @@
1
- const {S,def,notNil,isObj,needObj,TYPES} = require('./types');
1
+ const {S,F,def,notNil,isObj,needObj,TYPES} = require('./types');
2
2
  const {InternalObjectId} = require('./objectid');
3
3
 
4
+ /**
5
+ * A pseudo-module describing everything associated with `core.Enum`
6
+ * @module @lumjs/core/enum
7
+ */
8
+
4
9
  // Internal id instances should never be exported.
5
- const ENUM_ID = new InternalObjectId({name: '$Enum'});
10
+ const EID = '@lumjs/core/enum';
11
+ const ENUM_ID = new InternalObjectId({name: EID});
12
+ const isEnum = ENUM_ID.isFunction();
13
+ const EOPT = Symbol(EID+':opts');
6
14
 
7
15
  /**
8
16
  * A function to build magic Enum objects.
9
17
  *
10
18
  * Like the built-in `Symbol`, this is called as a function, not a constructor.
11
19
  *
20
+ * This is the *actual* exported value from the `enum` pseudo-module.
21
+ *
12
22
  * @param {(string[]|object)} spec - May be in one of two formats:
13
23
  *
14
24
  * - An `Array` of strings. Each string represents the name of an Enum
@@ -24,7 +34,13 @@ const ENUM_ID = new InternalObjectId({name: '$Enum'});
24
34
  *
25
35
  * @param {object} [opts] Options for creating the Enum
26
36
  *
27
- * @param {bool} [opts.symbols=false] Use `Symbol` property values.
37
+ * If the `opts` is an _open_ `Enum` object, then
38
+ *
39
+ * @param {(bool|function)} [opts.symbols=false] Use `Symbol` property values.
40
+ *
41
+ * If this is a `function`, it will be passed the name that would be used as
42
+ * the symbol key, and must return the actual key string to use, or any
43
+ * non-string value to disable the use of symbols for that name.
28
44
  *
29
45
  * @param {bool} [opts.globals=false] Use `Symbol.for()` property values.
30
46
  *
@@ -45,18 +61,24 @@ const ENUM_ID = new InternalObjectId({name: '$Enum'});
45
61
  *
46
62
  * The default value is `1` if `opts.flags` is `true` or `0` otherwise.
47
63
  *
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.
64
+ * @param {bool} [opts.open=false] Should the Enum be open to be extended?
65
+ *
66
+ * - If `true` the Enum object won't be locked, and the `opts` will be saved.
67
+ * - If `false` (default value) we use `Object.freeze()` to lock the object.
50
68
  *
51
69
  * @param {(bool|object|Array)} [opts.lock=null] If this is *not* `null`,
52
70
  * use {@see module:@lumjs/core/obj.lock lock()} instead of `Object.freeze()`.
53
71
  *
54
- * Only used if `opts.open` is `false`. The supported values are:
72
+ * If `opts.open` is `false`, the supported values are:
55
73
  *
56
74
  * - `Array`: The `[cloneable, cloneOpts, useSeal]` parameters for `clone()`.
57
75
  * - `object`: The `cloneOpts` parameter for `clone()`.
58
76
  * - `bool`: The `cloneable` parameter for `clone()`.
59
77
  *
78
+ * If `opts.open` is `true`, this only supports an `object` which will be
79
+ * used as the `opts` for {@see module:@lumjs/core/obj.addLock addLock()},
80
+ * which will be called on the open Enum object.
81
+ *
60
82
  * @param {bool} [opts.configurable=false] Enum properties are configurable?
61
83
  *
62
84
  * This option is ignored if `opts.open` is `false`.
@@ -65,29 +87,44 @@ const ENUM_ID = new InternalObjectId({name: '$Enum'});
65
87
  *
66
88
  * @returns {object} A magic Enum object.
67
89
  * @throws {TypeError} If an invalid value was passed.
68
- * @exports module:@lumjs/core/enum
90
+ * @alias module:@lumjs/core/enum.Enum
69
91
  */
70
92
  function Enum (spec, opts={})
71
93
  {
72
94
  needObj(spec, "Enum spec must be an object")
73
95
  needObj(opts, "Enum options must be an object")
74
96
 
75
- const anEnum = ENUM_ID.tag({});
97
+ const isExisting = isEnum(opts);
98
+ const anEnum = isExisting ? opts : ENUM_ID.tag({});
99
+ if (isExisting)
100
+ {
101
+ opts = anEnum[EOPT];
102
+ if (!isObj(opts))
103
+ {
104
+ throw new Error("existing Enum was not open for changes");
105
+ }
106
+ }
76
107
 
77
108
  const configurable = opts.configurable ?? false;
78
109
  const enumerable = opts.enumerable ?? true;
79
110
 
111
+ const symKey
112
+ = (typeof opts.symbols === F)
113
+ ? opts.symbols
114
+ : v => v;
115
+
80
116
  function getVal (name, def)
81
117
  {
82
118
  if (opts.symbols)
83
119
  { // We want to use symbols.
120
+ const key = symKey(name);
84
121
  if (opts.globals)
85
122
  {
86
- return Symbol.for(name);
123
+ return Symbol.for(key);
87
124
  }
88
125
  else
89
126
  {
90
- return Symbol(name);
127
+ return Symbol(key);
91
128
  }
92
129
  }
93
130
  else
@@ -130,6 +167,10 @@ function Enum (spec, opts={})
130
167
  counter++;
131
168
  }
132
169
  }
170
+ if (opts.open)
171
+ { // Save the last counter value into the opts
172
+ opts.counter = counter;
173
+ }
133
174
  }
134
175
  else
135
176
  { // An object mapping of property name to value.
@@ -141,7 +182,18 @@ function Enum (spec, opts={})
141
182
  }
142
183
  }
143
184
 
144
- if (!opts.open)
185
+ if (opts.open)
186
+ {
187
+ if (!isExisting)
188
+ { // Save the options into a special Symbol property.
189
+ def(anEnum, EOPT, {value: opts});
190
+ if (isObj(opts.lock))
191
+ { // Add lock() method.
192
+ addLock(anEnum, opts.lock);
193
+ }
194
+ }
195
+ }
196
+ else
145
197
  {
146
198
  if (notNil(opts.lock))
147
199
  { // Use lock() function.
@@ -180,13 +232,11 @@ function Enum (spec, opts={})
180
232
  * @param {*} obj - The expected object/function to test.
181
233
  * @returns {boolean}
182
234
  */
183
- const isEnum = ENUM_ID.isFunction()
184
235
  def(Enum, 'is', isEnum);
185
236
 
186
237
  /**
187
238
  * Is a value an *Enum* magic object?
188
- * @name module:@lumjs/core/types.isEnum
189
- * @function
239
+ * @function module:@lumjs/core/types.isEnum
190
240
  * @param {*} v - The value to test.
191
241
  * @returns {boolean}
192
242
  * @see module:@lumjs/core/enum.is
@@ -202,4 +252,4 @@ TYPES.add('ENUM', 'enum', isEnum, 'isEnum');
202
252
  module.exports = Enum;
203
253
 
204
254
  // Loading this at the end.
205
- const {lock} = require('./obj/lock');
255
+ const {lock,addLock} = require('./obj/lock');
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+
3
+ const {F,isObj} = require('../types');
4
+
5
+ /**
6
+ * An Event object to emit to handler callbacks.
7
+ *
8
+ * @prop {module:@lumjs/core/events.Listener} eventListener
9
+ * The event Listener instance this event was emitted from.
10
+ * @prop {string} name - The event name that was triggered.
11
+ * @prop {object} target - Target object for this event.
12
+ * @prop {Array} args - Arguments passed to `emit()`
13
+ * @prop {object} options - Composes options from the
14
+ * Registry and the Listener. Listener options take priority.
15
+ * @prop {?object} data
16
+ * If `args[0]` is any kind of `object` other than another Event
17
+ * instance, it will be used as the `data` property.
18
+ * If `args[0]` is not an `object`, this property will be `null`.
19
+ * @prop {module:@lumjs/core/events.Event} origEvent
20
+ * Unless `this.prevEvent` is set, this should always be
21
+ * a reference to `this` instance itself.
22
+ * @prop {?module:@lumjs/core/events.Event} prevEvent
23
+ * If `args[0]` is another Event instance this property
24
+ * will be set with its value, as well as the following
25
+ * changes to the default behavior:
26
+ *
27
+ * - `this.data` will be set to `prevEvent.data`.
28
+ * - `this.origEvent` will be set to `prevEvent.origEvent`
29
+ *
30
+ * @prop {module:@lumjs/core/events~Status} emitStatus
31
+ *
32
+ * @alias module:@lumjs/core/events.Event
33
+ */
34
+ class LumEvent
35
+ {
36
+ /**
37
+ * Create a new Event instance; should not be called directly.
38
+ * @protected
39
+ * @param {module:@lumjs/core/events.Listener} listener
40
+ * @param {object} target
41
+ * @param {string} name
42
+ * @param {Array} args
43
+ * @param {object} status
44
+ */
45
+ constructor(listener, target, name, args, status)
46
+ {
47
+ const reg = listener.registry;
48
+ this.eventListener = listener;
49
+ this.args = args;
50
+ this.target = target;
51
+ this.name = name;
52
+ this.emitStatus = status;
53
+ this.options = Object.assign({},
54
+ reg.options,
55
+ listener.options,
56
+ status.options);
57
+
58
+ this.data = null;
59
+ this.prevEvent = null;
60
+ this.origEvent = this;
61
+
62
+ if (isObj(args[0]))
63
+ { // The first argument is an object.
64
+ const ao = args[0];
65
+ if (ao instanceof LumEvent)
66
+ { // A previous event.
67
+ this.prevEvent = ao;
68
+ this.origEvent = ao.origEvent;
69
+ this.data = ao.data;
70
+ }
71
+ else
72
+ { // Use it as a data object.
73
+ this.data = ao;
74
+ }
75
+ }
76
+
77
+ if (typeof this.options.setupEvent === F)
78
+ {
79
+ this.options.setupEvent.call(listener, this);
80
+ }
81
+
82
+ }
83
+ }
84
+
85
+ module.exports = LumEvent;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * A simplistic event registration and dispatch module.
3
+ *
4
+ * Designed to be a replacement for the `observable` API which started
5
+ * out simple enough (when I forked it from `riot.js`), but has since
6
+ * grown into a big mess with many unusual and unexpected behaviours.
7
+ *
8
+ * So this has been designed to work with a cleaner, more consistent,
9
+ * yet extremely flexible event model.
10
+ *
11
+ * @module @lumjs/core/events
12
+ */
13
+ exports = module.exports =
14
+ {
15
+ Registry: require('./registry'),
16
+ Listener: require('./listener'),
17
+ Event: require('./event'),
18
+
19
+ /**
20
+ * A shortcut function to create a new Registry instance.
21
+ * All arguments are passed to the Registry constructor.
22
+ * @param {(object|module:@lumjs/core/events~GetTargets)} targets
23
+ * @param {object} [opts]
24
+ * @returns {module:@lumjs/core/events.Registry}
25
+ */
26
+ register()
27
+ {
28
+ return new exports.Registry(...arguments);
29
+ },
30
+ }
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+
3
+ const {F,isObj} = require('../types');
4
+ const Event = require('./event');
5
+
6
+ const REMOVE_OPTS = ['listener','handler','eventNames'];
7
+
8
+ function makeOpts(spec)
9
+ {
10
+ const opts = Object.assign({}, spec, spec.options);
11
+ for (const rm of REMOVE_OPTS)
12
+ {
13
+ delete opts[rm];
14
+ }
15
+ return opts;
16
+ }
17
+
18
+ /**
19
+ * Is something a valid value for an event listener?
20
+ *
21
+ * Valid listener values are inspired by the `DOM.EventTarget` interface:
22
+ *
23
+ * - A `function`
24
+ * - An `object` with a `handleEvent()` method
25
+ *
26
+ * @param {*} v - Value we are testing
27
+ * @returns {boolean}
28
+ * @alias module:@lumjs/core/events.Listener.isListener
29
+ */
30
+ function isListener(v)
31
+ {
32
+ return (typeof v === F || (isObj(v) && typeof v.handleEvent === F));
33
+ }
34
+
35
+ /**
36
+ * An Event Listener instance used by a Registry
37
+ *
38
+ * Used internally by the Registry class, there's likely very few
39
+ * reasons you'd want to call any methods on this manually.
40
+ *
41
+ * @prop {module:@lumjs/core/events.Registry} registry
42
+ * The Registry instance this Listener belongs to.
43
+ * @prop {(function|object)} handler - Event handler callback
44
+ * @prop {Set} eventNames - A set of all event names handled by this
45
+ * @prop {object} options - Options specific to this listener.
46
+ *
47
+ * See {@link module:@lumjs/core/events.Registry#makeListener makeListener()}
48
+ * for details on what this may contain and how it is populated.
49
+ *
50
+ * @alias module:@lumjs/core/events.Listener
51
+ */
52
+ class LumEventListener
53
+ {
54
+ /**
55
+ * Build a listener; called by Registry instance
56
+ * @private
57
+ * @param {module:@lumjs/core/events.Registry} registry
58
+ * @param {object} spec
59
+ */
60
+ constructor(registry, spec)
61
+ {
62
+ if (isListener(spec.listener))
63
+ {
64
+ this.handler = spec.listener;
65
+ }
66
+ else if (isListener(spec.handler))
67
+ {
68
+ this.handler = spec.handler;
69
+ }
70
+ else
71
+ {
72
+ console.error({spec,registry});
73
+ throw new TypeError("Invalid listener/handler in spec");
74
+ }
75
+
76
+ // Assign the rest here.
77
+ this.registry = registry;
78
+ this.options = makeOpts(spec);
79
+ this.eventNames = registry.getEventNames(spec.eventNames);
80
+ }
81
+
82
+ /**
83
+ * See if there is at least one item in `this.eventNames`
84
+ * @type {boolean}
85
+ */
86
+ get hasEvents()
87
+ {
88
+ return this.eventNames.size > 0;
89
+ }
90
+
91
+ /**
92
+ * Used by {@link module:@lumjs/core/events.Registry#emit emit()} to create
93
+ * and emit a new Event instance for a specified event name and target.
94
+ *
95
+ * This is a *protected method* and should not be called directly.
96
+ * @protected
97
+ * @param {string} eventName - A single event name that was triggered
98
+ * @param {object} target - A single target object
99
+ * @param {Array} args - Arguments passed to `emit()`
100
+ * @param {module:@lumjs/core/events~Status} status - Emit status info
101
+ * @returns {module:@lumjs/core/events.Event} The new Event that was emitted
102
+ */
103
+ emitEvent(eventName, target, args, status)
104
+ {
105
+ const event = new Event(this, target, eventName, args, status);
106
+
107
+ if (typeof this.handler === F)
108
+ { // The simplest is the good old function
109
+ this.handler.call(event.target, event);
110
+ }
111
+ else
112
+ { // An object with a `handleEvent()` method
113
+ this.handler.handleEvent(event);
114
+ }
115
+
116
+ if (event.options.once)
117
+ { // This listener is to be removed
118
+ status.onceRemoved.add(this);
119
+ }
120
+
121
+ return event;
122
+ }
123
+
124
+ }
125
+
126
+ LumEventListener.isListener = isListener;
127
+ module.exports = LumEventListener;