@lumjs/core 1.31.0 → 1.31.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.
@@ -23,8 +23,20 @@ exports = module.exports =
23
23
  * @param {object} [opts]
24
24
  * @returns {module:@lumjs/core/events.Registry}
25
25
  */
26
- register()
26
+ register(targets, opts)
27
27
  {
28
- return new exports.Registry(...arguments);
28
+ return new exports.Registry(targets, opts);
29
+ },
30
+
31
+ /**
32
+ * Create a registry for a single target object, then return the target.
33
+ * @param {object} target
34
+ * @param {object} [opts]
35
+ * @returns {object} `target`
36
+ */
37
+ extend(target, opts)
38
+ {
39
+ exports.register(target, opts);
40
+ return target;
29
41
  },
30
42
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const {S,F,isObj,def,isIterable} = require('../types');
4
4
  const Listener = require('./listener');
5
+ const RegSym = Symbol('@lumjs/core/events:registry');
5
6
 
6
7
  const DEF_EXTENDS =
7
8
  {
@@ -19,6 +20,53 @@ const DEF_OPTIONS =
19
20
  wildcard: '*',
20
21
  }
21
22
 
23
+ /**
24
+ * Has a target object been registered with an event registry?
25
+ * @param {object} target
26
+ * @returns {boolean}
27
+ * @alias module:@lumjs/core/events.Registry.isRegistered
28
+ */
29
+ const isRegistered = target => isObj(target[RegSym]);
30
+
31
+ /**
32
+ * Get event registry metadata from a target
33
+ * @private
34
+ * @param {object} target - Object to get metadata for
35
+ * @param {boolean} [create=false] Create metadata if it's not found?
36
+ * @returns {object} metadata (TODO: schema docs)
37
+ * @alias module:@lumjs/core/events.Registry.getMetadata
38
+ */
39
+ function getMetadata(target, create=false)
40
+ {
41
+ if (isRegistered(target))
42
+ { // Existing registry metadata found
43
+ return target[RegSym];
44
+ }
45
+ else if (create)
46
+ { // Create new metadata
47
+ const tpm =
48
+ {
49
+ r: new Map(),
50
+ p: {},
51
+ }
52
+ def(target, RegSym, tpm);
53
+ return tpm;
54
+ }
55
+ }
56
+
57
+ function targetsAre(targets)
58
+ {
59
+ const isaSet = (targets instanceof Set);
60
+ const isaArr = (!isaSet && Array.isArray(targets));
61
+ const tis =
62
+ {
63
+ set: isaSet,
64
+ array: isaArr,
65
+ handled: (isaSet || isaArr),
66
+ }
67
+ return tis;
68
+ }
69
+
22
70
  /**
23
71
  * A class that handles events for target objects
24
72
  *
@@ -40,29 +88,17 @@ class LumEventRegistry
40
88
  *
41
89
  * @param {(object|module:@lumjs/core/events~GetTargets)} targets
42
90
  *
43
- * If this is a `function`, it will be called to dynamically get a
44
- * list of target objects whenever an event is triggered.
45
- *
46
91
  * If this is an `object`, then any kind of `Iterable` may be used
47
92
  * to represent multiple targets, while any non-Iterable object will
48
- * be considered as single target.
93
+ * be considered as single target.
94
+ *
95
+ * If this is a `function`, it will be called to dynamically get a
96
+ * list of target objects whenever an event is triggered.
49
97
  *
50
98
  * @param {object} [opts] Options (saved to `options` property).
51
99
  *
52
100
  * @param {(RegExp|string)} [opts.delimiter=/\s+/] Used to split event names
53
101
  *
54
- * @param {boolean} [opts.multiMatch=false]
55
- * If a registered listener has multiple event names, and a call
56
- * to `emit()` also has multiple event names, the value of this
57
- * option will determine if the same listener will have its
58
- * handler function called more than once.
59
- *
60
- * If this is `true`, the handler will be called once for every
61
- * combination of target and event name.
62
- *
63
- * If this is `false` (default), then only the first matching event
64
- * name will be called for each target.
65
- *
66
102
  * @param {(object|boolean)} [opts.extend]
67
103
  * This option determines the rules for adding wrapper methods and
68
104
  * other extension properties to the target objects.
@@ -81,6 +117,18 @@ class LumEventRegistry
81
117
  * @param {?string} [opts.extend.listen="on"] `listen()` proxy method
82
118
  * @param {?string} [opts.extend.once=null] `once()` proxy method
83
119
  * @param {?string} [opts.extend.remove=null] `remove()` proxy method
120
+ *
121
+ * @param {boolean} [opts.multiMatch=false]
122
+ * If a registered listener has multiple event names, and a call
123
+ * to `emit()` also has multiple event names, the value of this
124
+ * option will determine if the same listener will have its
125
+ * handler function called more than once.
126
+ *
127
+ * If this is `true`, the handler will be called once for every
128
+ * combination of target and event name.
129
+ *
130
+ * If this is `false` (default), then only the first matching event
131
+ * name will be called for each target.
84
132
  *
85
133
  * @param {boolean} [opts.overwrite=false] Overwrite existing properties?
86
134
  *
@@ -106,55 +154,89 @@ class LumEventRegistry
106
154
  let defExt; // Default opts.extend value
107
155
  if (typeof targets === F)
108
156
  { // A dynamic getter method
157
+ this.funTargets = true;
109
158
  this.getTargets = targets;
159
+ targets = this.getTargets();
110
160
  defExt = false;
111
161
  }
112
162
  else
113
163
  { // Simple getter for a static value
114
- if (!isIterable(targets))
164
+ if (!(targets instanceof Set))
115
165
  {
116
- targets = [targets];
166
+ if (!isIterable(targets))
167
+ targets = [targets];
168
+ targets = new Set(targets);
117
169
  }
170
+
171
+ this.funTargets = false;
118
172
  this.getTargets = () => targets;
119
173
  defExt = true;
120
174
  }
121
175
 
122
- this.options = Object.assign({}, DEF_OPTIONS, opts);
176
+ this.options = Object.assign({extend: defExt}, DEF_OPTIONS, opts);
123
177
 
124
178
  this.allListeners = new Set();
125
179
  this.listenersFor = new Map();
126
180
 
127
- const extOpts = opts.extend ?? defExt;
128
- if (extOpts !== false)
181
+ this.extend(targets);
182
+ } // constructor()
183
+
184
+ /**
185
+ * Add extension methods to target objects;
186
+ * used by `constructor` and `register()`,
187
+ * not meant to be called from outside code.
188
+ * @private
189
+ * @param {Iterable} targets - Targets to extend
190
+ * @returns {module:@lumjs/core/events.Registry} `this`
191
+ */
192
+ extend(targets)
193
+ {
194
+ const opts = this.options;
195
+ const extOpts = opts.extend;
196
+
197
+ let intNames = null, extNames = null;
198
+
199
+ if (extOpts)
129
200
  {
130
- const intNames = Object.keys(DEF_EXTENDS);
131
- const extNames = Object.assign({}, DEF_EXTENDS, extOpts);
132
- const targets = this.getTargets();
201
+ intNames = Object.keys(DEF_EXTENDS);
202
+ extNames = Object.assign({}, DEF_EXTENDS, extOpts);
203
+ }
204
+
205
+ for (const target of targets)
206
+ {
207
+ const tps = {}, tpm = getMetadata(target, true);
208
+ tpm.r.set(this, tps);
133
209
 
134
- for (const iname of intNames)
210
+ if (extOpts)
135
211
  {
136
- if (typeof extNames[iname] === S && extNames[iname].trim() !== '')
212
+ for (const iname of intNames)
137
213
  {
138
- const ename = extNames[iname];
139
- const value = iname === 'registry'
140
- ? this // The registry instance itself
141
- : (...args) => this[iname](...args) // A proxy method
142
- for (const target of targets)
214
+ if (typeof extNames[iname] === S && extNames[iname].trim() !== '')
143
215
  {
144
- if (opts.overwrite || target[ename] === undefined)
216
+ const ename = extNames[iname];
217
+ const value = iname === 'registry'
218
+ ? this // The registry instance itself
219
+ : (...args) => this[iname](...args) // A proxy method
220
+ for (const target of targets)
145
221
  {
146
- def(target, ename, {value});
147
- }
148
- else
149
- {
150
- console.error("Won't overwrite existing property",
151
- {target,iname,ename,registry: this});
222
+ if (opts.overwrite || target[ename] === undefined)
223
+ {
224
+ def(target, ename, {value});
225
+ tps[ename] = iname;
226
+ tpm.p[ename] = this;
227
+ }
228
+ else
229
+ {
230
+ console.error("Won't overwrite existing property",
231
+ {target,iname,ename,registry: this});
232
+ }
152
233
  }
153
234
  }
154
235
  }
155
236
  }
156
237
  }
157
- } // constructor()
238
+ return this;
239
+ }
158
240
 
159
241
  /**
160
242
  * Build a new Listener instance; used by `listen()` method.
@@ -491,6 +573,119 @@ class LumEventRegistry
491
573
  return sti;
492
574
  }
493
575
 
576
+
577
+
578
+ /**
579
+ * Register additional target objects
580
+ * @param {...object} addTargets - Target objects to register
581
+ * @returns {module:@lumjs/core/events.Registry} `this`
582
+ */
583
+ register(...addTargets)
584
+ {
585
+ const allTargets = this.getTargets();
586
+ const tis = targetsAre(allTargets);
587
+
588
+ if (tis.handled)
589
+ {
590
+ for (const target of addTargets)
591
+ {
592
+ if (tis.set)
593
+ {
594
+ allTargets.add(target);
595
+ }
596
+ else if (tis.array)
597
+ {
598
+ if (allTargets.indexOf(target) === -1)
599
+ {
600
+ allTargets.push(target);
601
+ }
602
+ }
603
+ }
604
+ }
605
+ else
606
+ {
607
+ if (!tis.handled)
608
+ {
609
+ console.warn("cannot add targets to collection",
610
+ {addTargets, allTargets, registry: this});
611
+ }
612
+ }
613
+
614
+ this.extend(addTargets);
615
+ return this;
616
+ }
617
+
618
+ /**
619
+ * Remove a target object from the registry.
620
+ *
621
+ * This will also remove any extension properties added to the
622
+ * target object by this registry instance.
623
+ *
624
+ * @param {...object} [delTargets] Targets to unregister
625
+ *
626
+ * If no targets are specified, this will unregister **ALL** targets
627
+ * from this registry!
628
+ *
629
+ * @returns {module:@lumjs/core/events.Registry} `this`
630
+ */
631
+ unregister(...delTargets)
632
+ {
633
+ const allTargets = this.getTargets();
634
+ const tis = targetsAre(allTargets);
635
+
636
+ if (delTargets.length === 0)
637
+ { // Unregister ALL targets.
638
+ delTargets = allTargets;
639
+ }
640
+
641
+ if (!tis.handled)
642
+ {
643
+ console.warn("cannot remove targets from collection",
644
+ {delTargets, allTargets, registry: this});
645
+ }
646
+
647
+ for (const target of delTargets)
648
+ {
649
+ if (!isRegistered(target)) continue;
650
+
651
+ const tpm = target[RegSym];
652
+ const tp = tpm.r.get(this);
653
+
654
+ if (tis.set)
655
+ {
656
+ allTargets.delete(target);
657
+ }
658
+ else if (tis.array)
659
+ {
660
+ const tin = allTargets.indexOf(target);
661
+ if (tin !== -1)
662
+ {
663
+ allTargets.splice(tin, 1);
664
+ }
665
+ }
666
+
667
+ if (isObj(tp))
668
+ { // Remove any added extension properties
669
+ for (const ep in tp)
670
+ {
671
+ if (tpm.p[ep] === this)
672
+ { // Remove it from the target.
673
+ delete target[ep];
674
+ delete tpm.p[ep];
675
+ }
676
+ }
677
+ tpm.r.delete(this);
678
+ }
679
+
680
+ if (tpm.r.size === 0)
681
+ { // No registries left, remove the metadata too
682
+ delete target[RegSym];
683
+ }
684
+ }
685
+
686
+ return this;
687
+ }
688
+
494
689
  /**
495
690
  * Get a Set of event names from various kinds of values
496
691
  * @param {(string|Iterable)} names - Event names source
@@ -534,4 +729,9 @@ class LumEventRegistry
534
729
 
535
730
  }
536
731
 
732
+ Object.assign(LumEventRegistry,
733
+ {
734
+ isRegistered, getMetadata, targetsAre,
735
+ });
736
+
537
737
  module.exports = LumEventRegistry;
package/lib/index.js CHANGED
@@ -39,6 +39,9 @@ def(exports, 'types', types);
39
39
  def(exports, 'def', def);
40
40
  def(exports, 'lazy', lazy);
41
41
 
42
+ // TODO: document
43
+ def(exports, 'state', require('./state'));
44
+
42
45
  /**
43
46
  * Array utility functions «Lazy»
44
47
  * @name module:@lumjs/core.arrays
package/lib/state.js ADDED
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ const {S,isObj} = require('./types')
4
+ const ctx = require('./context')
5
+ const stateSym = Symbol('@lumjs/core/state')
6
+ const stateData = {[stateSym]: false}
7
+ const stateKey = 'LUM_JS_STATE'
8
+ const stateOpts = {}
9
+
10
+ module.exports =
11
+ {
12
+ get(opts={})
13
+ {
14
+ if (opts.refresh || !stateData[stateSym])
15
+ {
16
+ let json;
17
+ if (ctx.isNode)
18
+ {
19
+ json = process.env[stateKey];
20
+ }
21
+ else if (ctx.isBrowser)
22
+ {
23
+ json = localStorage.getItem(stateKey);
24
+ }
25
+
26
+ if (typeof json === S)
27
+ {
28
+ const revive = opts.jsonRevive ?? stateOpts.jsonRevive;
29
+ const storedData = JSON.parse(json, revive);
30
+ if (isObj(storedData))
31
+ {
32
+ Object.assign(stateData, storedData);
33
+ stateData[stateSym] = true;
34
+ }
35
+ }
36
+ }
37
+ return stateData;
38
+ },
39
+
40
+ save(opts={})
41
+ {
42
+ if (!stateData[stateSym]) return false;
43
+
44
+ const replace = opts.jsonReplace ?? stateOpts.jsonReplace;
45
+ const json = JSON.stringify(stateData, replace);
46
+
47
+ if (ctx.isBrowser)
48
+ {
49
+ localStorage.setItem(stateKey, json);
50
+ }
51
+ else if (ctx.isNode)
52
+ {
53
+ console.log("export ", stateKey, "=", json);
54
+ }
55
+ },
56
+
57
+ opts: stateOpts,
58
+
59
+ [stateSym]: stateData,
60
+ $$: stateSym,
61
+ }
package/lum.build.js CHANGED
@@ -1,40 +1,11 @@
1
- "use strict";
2
-
3
- const D =
1
+ module.exports = function(rb)
4
2
  {
5
- build: 'docs/build',
6
- api: 'docs/api/',
7
- changelogs: 'docs/changelogs',
8
- ad(from)
9
- {
10
- const to = D.api+from.replace(/\.md/, '.html');
11
- return {from,to}
12
- },
13
- }
14
-
15
- const dirs =
16
- [
17
- D.build,
18
- D.api+D.changelogs,
19
- ]
20
-
21
- const header = "<html><body>";
22
- const footer = "</body></html>";
23
-
24
- const files =
25
- [
26
- {
27
- from: 'README.md',
28
- to: D.build+'/README.md',
29
- },
30
- D.ad('docs/TODO.md'),
31
- D.ad(D.changelogs+'/index.md'),
32
- D.ad(D.changelogs+'/1.0-beta.md'),
33
- D.ad(D.changelogs+'/1.x.md'),
34
- D.ad(D.changelogs+'/2.x.md'),
35
- ]
36
-
37
- module.exports =
38
- {
39
- dirs, files, header, footer,
3
+ rb.readme
4
+ .add('docs/TODO.md')
5
+ .chdir('docs/changelogs')
6
+ .add('index.md')
7
+ .add('1.0-beta.md')
8
+ .add('1.x.md')
9
+ .add('2.x.md')
10
+ //.set('debug', true)
40
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.31.0",
3
+ "version": "1.31.1",
4
4
  "main": "lib/index.js",
5
5
  "exports":
6
6
  {
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "devDependencies":
34
34
  {
35
- "@lumjs/build": "^1.0.0",
35
+ "@lumjs/build": "^1.1.0",
36
36
  "@lumjs/tests": "^2.0.0"
37
37
  },
38
38
  "scripts":