solarite 0.1.0 → 0.2.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.
Files changed (48) hide show
  1. package/benchmarks/naive/Solarite.min.js +4 -0
  2. package/benchmarks/naive/index.html +14 -0
  3. package/benchmarks/naive/main.js +339 -0
  4. package/benchmarks/naive/package-lock.json +13 -0
  5. package/benchmarks/naive/package.json +23 -0
  6. package/benchmarks/readme.md +48 -0
  7. package/build/build.bat +3 -2
  8. package/build/build.js +1 -1
  9. package/dist/Solarite-debug.js +1941 -2791
  10. package/dist/Solarite.js +1697 -2511
  11. package/dist/Solarite.min.js +3 -3
  12. package/dist/udomdiff-license.txt +18 -0
  13. package/docs/index.md +695 -235
  14. package/docs/js/Playground.js +1 -1
  15. package/docs/js/codemirror/codemirror6.js +3683 -3206
  16. package/docs/js/codemirror/themeSolarIce.js +2 -2
  17. package/docs/js/documentation.js +2 -2
  18. package/docs/js/ui/CodeEditor.js +183 -45
  19. package/docs/js/ui/FlexResizer.js +15 -5
  20. package/docs/js/util/Errors.js +11 -0
  21. package/docs/media/documentation.css +6 -3
  22. package/index.html +462 -26
  23. package/package.json +1 -1
  24. package/readme.md +11 -1
  25. package/src/solarite/ExprPath.js +422 -135
  26. package/src/solarite/Globals.js +53 -0
  27. package/src/solarite/NodeGroup.js +300 -388
  28. package/src/solarite/Shell.js +31 -33
  29. package/src/solarite/Solarite.js +17 -2
  30. package/src/solarite/Template.js +75 -25
  31. package/src/solarite/Util.js +131 -7
  32. package/src/solarite/createSolarite.js +32 -25
  33. package/src/solarite/getArg.js +12 -12
  34. package/src/solarite/hash.js +18 -35
  35. package/src/solarite/r.js +128 -118
  36. package/src/solarite/watch3.js +98 -0
  37. package/src/{solarite → unused}/NodeGroupManager.js +86 -224
  38. package/src/unused/onConnect.js +79 -0
  39. package/src/{solarite → unused}/watch.js +2 -2
  40. package/src/{solarite → unused}/watch2.js +4 -4
  41. package/src/{solarite → util}/MultiValueMap.js +23 -12
  42. package/src/util/WeakArray.js +33 -0
  43. package/tests/Solarite.test.js +1108 -166
  44. package/tests/Testimony.js +274 -67
  45. package/tests/index.html +6 -35
  46. package/tests/run.bat +2 -0
  47. package/tests/NodeGroup.test.js +0 -115
  48. package/tests/Shell.test.js +0 -75
@@ -1,24 +1,12 @@
1
1
  import MultiValueMap from "./MultiValueMap.js";
2
2
  import NodeGroup from "./NodeGroup.js";
3
3
  import {getObjectHash} from "./hash.js";
4
- import {serializePath} from "./watch.js";
5
4
 
6
5
  import {assert} from "../util/Errors.js";
6
+ import Globals from "./Globals.js";
7
+ import Template from "./Template.js";
7
8
 
8
9
 
9
- /**
10
- * @typedef {Object} RenderOptions
11
- * @property {boolean=} styles - Indicates whether the Courage component is present.
12
- * @property {boolean=} scripts - Indicates whether the Power component is present.
13
- * @property {boolean=} ids
14
- *
15
- * @property {?boolean} render
16
- * Used only when options are given to a class super constructor inheriting from Solarite.
17
- * True to call render() immediately in super constructor.
18
- * False to automatically call render() at all.
19
- * Undefined (default) to call render() when added to the DOM, unless already rendered.
20
- */
21
-
22
10
 
23
11
  /**
24
12
  * Manage all the NodeGroups for a single WebComponent or root HTMLElement
@@ -26,7 +14,7 @@ import {assert} from "../util/Errors.js";
26
14
  * And each NodeGroup manages the one or more nodes created by the expression.
27
15
  *
28
16
  * An instance of this class exists for each element that r() renders to. */
29
- export default class NodeGroupManager {
17
+ class NodeGroupManager {
30
18
 
31
19
  /** @type {HTMLElement|DocumentFragment} */
32
20
  rootEl;
@@ -47,13 +35,17 @@ export default class NodeGroupManager {
47
35
  /**
48
36
  * A map from the html strings and exprs that created a node group, to the NodeGroup.
49
37
  * Also stores a map from just the html strings to the NodeGroup, so we can still find a similar match if the exprs changed.
50
- *
51
38
  * @type {MultiValueMap<string, (string|Template)[], NodeGroup>} */
52
39
  nodeGroupsAvailable = new MultiValueMap();
40
+
41
+ /**
42
+ * Save the NodeGroups that are returned by NodeGroupManager.get()
43
+ * Calling reset() at the end of render puts them all back into nodeGroupsAvailable.
44
+ * @type {NodeGroup[]} */
53
45
  nodeGroupsInUse = [];
54
46
 
55
47
 
56
- /** @type {RenderOptions} */
48
+ /** @type {RenderOptions} TODO: Where is this ever used? */
57
49
  options = {};
58
50
 
59
51
 
@@ -66,16 +58,19 @@ export default class NodeGroupManager {
66
58
  * @param rootEl {HTMLElement|DocumentFragment} If not specified, the first element of the html will be the rootEl. */
67
59
  constructor(rootEl=null) {
68
60
  this.rootEl = rootEl;
61
+ this.resetModifications();
62
+
69
63
  /*
70
64
  //#IFDEV
71
-
65
+
66
+ // Use MutationWatcher to check for illegal DOM modifications.
72
67
  function closestCustomElement(node) {
73
68
  do {
74
69
  if (node.tagName && node.tagName.includes('-'))
75
70
  return node;
76
71
  } while (node = node.parentNode);
77
72
  }
78
-
73
+
79
74
  // TODO: Only trigger if we modify nodes inside an ExprPath.
80
75
  // TODO: Enable this even when not in dev mode, because it's so useful for debugging?
81
76
  // But it modifies the top level prototypes.
@@ -107,126 +102,6 @@ export default class NodeGroupManager {
107
102
  */
108
103
  }
109
104
 
110
- /**
111
- * Render the main template, which may indirectly call renderTemplate() to create children.
112
- * @param template {Template}
113
- * @param options {RenderOptions}
114
- * @return {?DocumentFragment} */
115
- render(template, options={}) {
116
- this.mutationWatcherEnabled = false;
117
- this.options = options;
118
- this.clearSubscribers = false;
119
-
120
- //#IFDEV
121
- this.modifications = {
122
- created: [],
123
- updated: [],
124
- moved: [],
125
- deleted: []
126
- };
127
- //#ENDIF
128
-
129
- if (!template && template !== '') {
130
- this.rootEl.outerHTML = '';
131
- this.mutationWatcherEnabled = true;
132
- return null;
133
- }
134
-
135
- // Fast path for empty component.
136
- if (template.html?.length === 1 && !template.html[0]) {
137
- this.rootEl.innerHTML = '';
138
- }
139
- else {
140
-
141
- // Find or create a NodeGroup for the template.
142
- // This updates all nodes from the template.
143
- let close;
144
- let exact = this.getNodeGroup(template, true);
145
- if (!exact) {
146
- close = this.getNodeGroup(template, false);
147
- }
148
-
149
-
150
- let firstTime = !this.rootNg;
151
- this.rootNg = exact || close;
152
-
153
- // Reparent NodeGroup
154
- // TODO: Move this to NodeGroup?
155
- let parent = this.rootNg.getParentNode()
156
- if (!this.rootEl)
157
- this.rootEl = parent;
158
-
159
- // If this is the first time rendering this element.
160
- else if (firstTime) {
161
-
162
- // Save slot children
163
- let fragment;
164
- if (this.rootEl.childNodes.length) {
165
- fragment = document.createDocumentFragment();
166
- fragment.append(...this.rootEl.childNodes);
167
- }
168
-
169
- // Add rendered elements.
170
- if (parent instanceof DocumentFragment)
171
- this.rootEl.append(parent);
172
- else if (parent)
173
- this.rootEl.append(...parent.childNodes)
174
-
175
- // Apply slot children
176
- if (fragment) {
177
- for (let slot of this.rootEl.querySelectorAll('slot[name]')) {
178
- let name = slot.getAttribute('name')
179
- if (name)
180
- slot.append(...fragment.querySelectorAll(`[slot='${name}']`))
181
- }
182
- let unamedSlot = this.rootEl.querySelector('slot:not([name])')
183
- if (unamedSlot)
184
- unamedSlot.append(fragment)
185
- }
186
-
187
- }
188
-
189
- // this.rootNg was rendered as childrenOnly=true
190
- // Apply attributes from a root element to the real root element.
191
- let ng = this.rootNg;
192
- if (ng.pseudoRoot && ng.pseudoRoot !== this.rootEl) {
193
- /*#IFDEV*/assert(this.rootEl)/*#ENDIF*/
194
-
195
- // Remove old attributes
196
- // for (let attrib of this.rootEl.attributes)
197
- // if (attrib.name !== 'is' && attrib.name !== 'data-style' && !ng.pseudoRoot.hasAttribute(attrib.name))
198
- // this.rootEl.removeAttribute(attrib.name)
199
-
200
- // Add/set new attributes
201
- if (firstTime)
202
- for (let attrib of ng.pseudoRoot.attributes)
203
- if (!this.rootEl.hasAttribute(attrib.name))
204
- this.rootEl.setAttribute(attrib.name, attrib.value);
205
-
206
- // ng.startNode = ng.endNode = this.rootEl;
207
- // ng.nodesCache = [ng.startNode]
208
- // for (let path of ng.paths) {
209
- // if (path.nodeMarker === ng.rootEl)
210
- // path.nodeMarker = this.rootEl;
211
- // path.nodesCache = null;
212
- // /*#IFDEV*/assert(path.nodeBefore !== ng.rootEl)/*#ENDIF*/
213
- // }
214
- //
215
- // ng.rootEl = this.rootEl;
216
- }
217
-
218
- /*#IFDEV*/this.rootNg.verify();/*#ENDIF*/
219
- this.reset();
220
- /*#IFDEV*/this.rootNg.verify();/*#ENDIF*/
221
- }
222
-
223
- this.mutationWatcherEnabled = true;
224
- return this.rootEl;
225
- //#IFDEV
226
- //return this.modifications;
227
- //#ENDIF
228
- }
229
-
230
105
 
231
106
  /**
232
107
  *
@@ -267,9 +142,10 @@ export default class NodeGroupManager {
267
142
  if (!ng2.inUse) {
268
143
  ng2.inUse = true;
269
144
  let success = this.nodeGroupsAvailable.delete(ng2.exactKey, ng2);
270
- // assert(success);
145
+ assert(success);
146
+
271
147
  let success2 = this.nodeGroupsAvailable.delete(ng2.closeKey, ng2);
272
- // assert(success);
148
+ assert(success2);
273
149
  /*#IFDEV*/assert(success === success2)/*#ENDIF*/
274
150
 
275
151
  // console.log(getHtml(ng2))
@@ -283,8 +159,10 @@ export default class NodeGroupManager {
283
159
  // Recurse to mark all child NodeGroups as in-use.
284
160
  for (let path of ng.paths)
285
161
  for (let childNg of path.nodeGroups) {
286
- if (!childNg.inUse)
162
+ if (!childNg.inUse) {
287
163
  this.findAndDeleteExact(childNg.exactKey, false, childNg);
164
+ // this.findAndDeleteClose(childNg.closeKey, childNg.exactKey, false);
165
+ }
288
166
  childNg.inUse = true;
289
167
  }
290
168
 
@@ -309,6 +187,7 @@ export default class NodeGroupManager {
309
187
  if (ng) {
310
188
 
311
189
  // We matched on a new key, so delete the old exactKey.
190
+ /*#IFDEV*/assert(ng.exactKey);/*#ENDIF*/
312
191
  let exactNg = this.nodeGroupsAvailable.delete(ng.exactKey, ng);
313
192
 
314
193
  /*#IFDEV*/assert(exactNg);/*#ENDIF*/
@@ -323,12 +202,16 @@ export default class NodeGroupManager {
323
202
  while (ng2 = ng2?.parentPath?.parentNg) {
324
203
  if (!ng2.inUse) {
325
204
  ng2.inUse = true; // Might speed it up slightly?
205
+ /*#IFDEV*/assert(ng2.exactKey);/*#ENDIF*/
326
206
  let success = this.nodeGroupsAvailable.delete(ng2.exactKey, ng2);
327
207
  /*#IFDEV*/assert(success);/*#ENDIF*/
328
208
 
329
- // But it can still be a close match, so we don't use this code.
209
+ // But it can still be a close match, so we remove it there too.
210
+ /*#IFDEV*/assert(ng2.closeKey);/*#ENDIF*/
330
211
  success = this.nodeGroupsAvailable.delete(ng2.closeKey, ng2);
331
212
  /*#IFDEV*/assert(success);/*#ENDIF*/
213
+
214
+ this.nodeGroupsInUse.push(ng2);
332
215
  }
333
216
  }
334
217
  }
@@ -362,59 +245,49 @@ export default class NodeGroupManager {
362
245
  * Get an existing or create a new NodeGroup that matches the template,
363
246
  * but don't reparent it if it's somewhere else.
364
247
  * @param template {Template}
365
- * @param exact {?boolean}
366
- * @param createForWatch
248
+ * @param exact {?boolean} If true, only get a NodeGroup if it matches both the template
249
+ * @param replaceMode {?boolean} If true, use the template to replace an existing element, instead of appending children to it.
367
250
  * @return {?NodeGroup} */
368
- getNodeGroup(template, exact=null, createForWatch=false) {
251
+ getNodeGroup(template, exact=null, replaceMode=null, exactKey=null) {
252
+ exactKey = exactKey || getObjectHash(template);
369
253
 
370
- let exactKey = getObjectHash(template)
371
-
372
- /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Looking for ${exact ? 'exact' : 'close'} match: ` + template.debug)/*#ENDIF*/
254
+ /*#IFDEV*/if(logEnabled) this.log(`Looking for ${exact ? 'exact' : 'close'} match: ` + template.debug)/*#ENDIF*/
373
255
 
374
256
  // 1. Try to find an exact match.
375
- let ng;
376
- if (exact === true) {
377
- ng = this.findAndDeleteExact(exactKey);
378
-
379
- if (!ng) {
380
- /*#IFDEV*/this.log(`Not found.`)/*#ENDIF*/
381
- return null;
382
- }
383
- /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Found exact: ` + ng.debug)/*#ENDIF*/
257
+ let ng = this.findAndDeleteExact(exactKey);
258
+ if (exact && !ng) {
259
+ /*#IFDEV*/this.log(`Not found.`)/*#ENDIF*/
260
+ return null;
384
261
  }
262
+ /*#IFDEV*/if (logEnabled) this.log(`Found exact: ` + ng.debug);/*#ENDIF*/
385
263
 
386
264
  // 2. Try to find a close match.
387
- else {
265
+ if (!ng) {
388
266
  // We don't need to delete the exact match bc it's already been deleted in the prev pass.
389
267
  let closeKey = template.getCloseKey();
390
- ng = createForWatch ? null : this.findAndDeleteClose(closeKey, exactKey);
268
+ ng = this.findAndDeleteClose(closeKey, exactKey);
391
269
 
392
270
  // 2. Update expression values if they've changed.
393
271
  if (ng) {
394
-
395
- // Temporary for debugging:
396
- if (window.debug && !window.ng)
397
- window.ng = ng;
398
-
399
- /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Found close: ` + closeKey + ' ' + ng.debug)/*#ENDIF*/
272
+ /*#IFDEV*/if (logEnabled) this.log(`Found close: ` + closeKey + ' ' + ng.debug);/*#ENDIF*/
400
273
  /*#IFDEV*/this.incrementLogDepth(1);/*#ENDIF*/
401
274
  /*#IFDEV*/ng.verify();/*#ENDIF*/
275
+
402
276
  ng.applyExprs(template.exprs);
403
277
 
404
- /*#IFDEV*/ng.verify()/*#ENDIF*/
278
+ /*#IFDEV*/ng.verify();/*#ENDIF*/
405
279
  /*#IFDEV*/this.incrementLogDepth(-1);/*#ENDIF*/
406
- /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Updated close to: ` + ng.debug)/*#ENDIF*/
280
+ /*#IFDEV*/if (logEnabled) this.log(`Updated close to: ` + ng.debug);/*#ENDIF*/
407
281
  }
408
282
 
409
283
  // 3. Or if not found, create a new NodeGroup
410
284
  else {
411
285
  /*#IFDEV*/this.incrementLogDepth(1);/*#ENDIF*/
412
- ng = new NodeGroup(template, this);
413
- /*#IFDEV*/this.incrementLogDepth(-1);/*#ENDIF*/
414
286
 
415
- //#IFDEV
416
- this.modifications.created.push(...ng.getNodes())
417
- //#ENDIF
287
+ ng = new NodeGroup(template, this, replaceMode, exactKey, closeKey);
288
+
289
+ /*#IFDEV*/this.incrementLogDepth(-1);/*#ENDIF*/
290
+ /*#IFDEV*/this.modifications.created.push(...ng.getNodes())/*#ENDIF*/
418
291
 
419
292
 
420
293
  // 4. Mark NodeGroup as being in-use.
@@ -423,12 +296,9 @@ export default class NodeGroupManager {
423
296
  // Perhaps also result could cache its last exprKey and then we'd use only one map?
424
297
  ng.exactKey = exactKey;
425
298
  ng.closeKey = closeKey;
426
- if (createForWatch) // TODO: Have this path be a separate function?
427
- this.nodeGroupsAvailable.add(ng.exactKey, ng);
428
- else
429
- this.nodeGroupsInUse.push(ng)
299
+ this.nodeGroupsInUse.push(ng)
430
300
 
431
- /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Created new ` + ng.debug)/*#ENDIF*/
301
+ /*#IFDEV*/if (logEnabled) this.log(`Created new ` + ng.debug)/*#ENDIF*/
432
302
  }
433
303
  }
434
304
 
@@ -446,7 +316,13 @@ export default class NodeGroupManager {
446
316
  return ng;
447
317
  }
448
318
 
319
+ /**
320
+ * Move everything in nodeGroupsInUse to nodeGroupsAvailable.
321
+ * This is called after the NodeGroup has finished rendering. */
449
322
  reset() {
323
+
324
+ /*#IFDEV*/this.rootNg.verify();/*#ENDIF*/
325
+
450
326
  //this.changes = [];
451
327
  let available = this.nodeGroupsAvailable
452
328
  for (let ng of this.nodeGroupsInUse) {
@@ -461,53 +337,41 @@ export default class NodeGroupManager {
461
337
 
462
338
  /*#IFDEV*/this.log('----------------------')/*#ENDIF*/
463
339
  // TODO: free the memory from any nodeGroupsAvailable() after render is done, since they weren't used?
464
- }
465
340
 
466
341
 
467
- // deprecated
468
- //pathToLoopInfo = new MultiValueMap(); // uses a Set() for each value.
469
- clearSubscribers = false;
470
-
471
- /**
472
- * One path may be used to loop in more than one place, so we use this to get every anchor from each loop.
473
- * @param path {Array}
474
- * @return {LoopInfo[]} A function that gets the loop anchor NodeGroup */
475
- getLoopInfo(path) {
476
- let serializedArrayPath = serializePath(path);
477
- return [...this.pathToLoopInfo.getAll(serializedArrayPath)]; // This is set inside forEach()
342
+ /*#IFDEV*/this.rootNg.verify();/*#ENDIF*/
478
343
  }
479
344
 
480
-
481
- /**
482
- * @deprecated
483
- * Store the functions used to create items for each loop.
484
- * TODO: Can this be combined with pathToTemplates?
485
- * @type {MultiValueMap<string, Subscriber>} */
486
- pathToLoopInfo = new MultiValueMap();
487
-
488
- /**
489
- * Maps variable paths to the templates used to create NodeGroups
490
- * @type {MultiValueMap<string, Subscriber>} */
491
- subscribers = new MultiValueMap();
492
-
493
- clearSubscribersIfNeeded() {
494
- if (this.clearSubscribers) {
495
- this.pathToLoopInfo = new MultiValueMap();
496
- this.subscribers = new MultiValueMap();
497
- this.clearSubscribers = false;
498
- }
345
+ resetModifications() {
346
+ this.modifications = {
347
+ created: [],
348
+ updated: [],
349
+ moved: [],
350
+ deleted: []
351
+ };
499
352
  }
500
-
501
353
 
502
354
  /**
503
- * Get the NodeGroupManager for a Web Component.
504
- * @param rootEl {Solarite|HTMLElement}
355
+ * Get the NodeGroupManager for a Web Component, given either its root element or its Template.
356
+ * If given a Template, create a new NodeGroup.
357
+ * Using a Template is typically used when creating a standalone component.
358
+ * @param rootEl {Solarite|HTMLElement|Template}
505
359
  * @return {NodeGroupManager} */
506
- static get(rootEl) {
507
- let ngm = nodeGroupManagers.get(rootEl);
508
- if (!ngm) {
509
- ngm = new NodeGroupManager(rootEl);
510
- nodeGroupManagers.set(rootEl, ngm);
360
+ static get(rootEl=null) {
361
+ let ngm;
362
+ if (rootEl instanceof Template) {
363
+ ngm = new NodeGroupManager();
364
+ ngm.rootNg = ngm.getNodeGroup(rootEl, false);
365
+ let el = ngm.rootNg.getRootNode();
366
+ Globals.nodeGroupManagers.set(el, ngm);
367
+ }
368
+ else {
369
+
370
+ ngm = Globals.nodeGroupManagers.get(rootEl);
371
+ if (!ngm) {
372
+ ngm = new NodeGroupManager(rootEl);
373
+ Globals.nodeGroupManagers.set(rootEl, ngm);
374
+ }
511
375
  }
512
376
 
513
377
  return ngm;
@@ -515,13 +379,13 @@ export default class NodeGroupManager {
515
379
 
516
380
 
517
381
  //#IFDEV
518
- static logEnabled = false;
382
+
519
383
  incrementLogDepth(level) {
520
384
  this.logDepth += level;
521
385
  }
522
386
  log(msg, level=0) {
523
387
  this.logDepth += level;
524
- if (NodeGroupManager.logEnabled) {
388
+ if (logEnabled) {
525
389
  let indent = ' '.repeat(this.logDepth);
526
390
  console.log(indent + msg);
527
391
  }
@@ -567,11 +431,9 @@ export default class NodeGroupManager {
567
431
 
568
432
  NodeGroupManager.pendingChildren = [];
569
433
 
570
- /**
571
- * Each Element that has Expr children has an associated NodeGroupManager here.
572
- * @type {WeakMap<HTMLElement, NodeGroupManager>} */
573
- let nodeGroupManagers = new WeakMap();
574
-
434
+ //#IFDEV
435
+ let logEnabled = false // Prevent rollup's static analyzer from removing this.
436
+ //#ENDIF
575
437
 
576
438
 
577
439
  export class LoopInfo {
@@ -0,0 +1,79 @@
1
+ // TODO: Everything here fails because our own connectedCallback function is never called.
2
+ // Perhaps b/c these callbacks must be defined before we register the custom element.
3
+
4
+
5
+ /**
6
+ * @type {Map<HTMLElement, Set<function(HTMLElement)>>} */
7
+ let connectCallbacks = new Map();
8
+ /**
9
+ * @type {Map<HTMLElement, Set<function(HTMLElement)>>} */
10
+ let firstConnectCallbacks = new Map();
11
+ /**
12
+ * @type {Map<HTMLElement, Set<function(HTMLElement)>>} */
13
+ let disconnectCallbacks = new Map();
14
+
15
+ function getElCallbacks(el, map) {
16
+ let callbacks = connectCallbacks.get(el);
17
+ if (!callbacks) {
18
+ callbacks = new Set();
19
+ connectCallbacks.set(el, callbacks);
20
+ }
21
+ return callbacks;
22
+ }
23
+
24
+ let createConnectedCallback = el => () => {
25
+ debugger;
26
+ for (let cb of getElCallbacks(el, connectCallbacks))
27
+ cb();
28
+
29
+ let cbs = getElCallbacks(el, firstConnectCallbacks);
30
+ for (let cb of cbs) {
31
+ cb();
32
+ cbs.delete(cb);
33
+ }
34
+ };
35
+
36
+
37
+ /**
38
+ *
39
+ * @param el {HTMLElement}
40
+ * @param callback {function}
41
+ * @return {function()} A function that removes the callback. */
42
+ export function onConnect(el, callback) {
43
+ let callbacks = getElCallbacks(el, connectCallbacks);
44
+ callbacks.add(callback);
45
+
46
+ if (!el.constructor.prototype.hasOwnProperty('connectedCallback'))
47
+ el.constructor.prototype.connectedCallback = createConnectedCallback(el);
48
+
49
+ return () => {
50
+ callbacks.delete(callback);
51
+ }
52
+ }
53
+
54
+ export function onFirstConnect(el, callback) {
55
+ let callbacks = getElCallbacks(el, firstConnectCallbacks);
56
+ callbacks.add(callback);
57
+
58
+ if (!el.hasOwnProperty('connectedCallback'))
59
+ el.connectedCallback = createConnectedCallback(el);
60
+
61
+ return () => {
62
+ callbacks.delete(callback);
63
+ }
64
+ }
65
+
66
+ export function onDisconnect(el, callback) {
67
+ let callbacks = getElCallbacks(el, disconnectCallbacks);
68
+ callbacks.add(callback);
69
+
70
+ if (!el.hasOwnProperty('connectedCallback'))
71
+ el.connectedCallback = () => {
72
+ for (let cb of callbacks)
73
+ cb();
74
+ }
75
+
76
+ return () => {
77
+ callbacks.delete(callback);
78
+ }
79
+ }
@@ -5,7 +5,7 @@ import {assert} from "../util/Errors.js";
5
5
  import delve from "../util/delve.js";
6
6
  import {getObjectHash, getObjectId} from "./hash.js";
7
7
  import MultiValueMap from "./MultiValueMap.js";
8
- import NodeGroupManager, {LoopInfo} from "./NodeGroupManager.js";
8
+ //import NodeGroupManager, {LoopInfo} from "./NodeGroupManager.js";
9
9
  import Template from "./Template.js";
10
10
 
11
11
 
@@ -136,7 +136,7 @@ export function forEach(arrayPath, callback) {
136
136
  ];
137
137
 
138
138
  // We return a template that wraps the array
139
- // So that NodeGroup.applyOneExpr can set the ExprPath and nextSibling on the template.
139
+ // So that ExprPath.apply() can set the ExprPath and nextSibling on the template.
140
140
  // Then the 'insert' path in renderWatched() uses that data fora dding more nodes.
141
141
  let result = new Template(['', ''], [newItems])
142
142
 
@@ -1,6 +1,6 @@
1
1
  import {assert} from "../util/Errors.js";
2
2
  import {getObjectId} from "./hash.js";
3
- import NodeGroupManager from "./NodeGroupManager.js";
3
+ //import NodeGroupManager from "./NodeGroupManager.js";
4
4
  import Template from "./Template.js";
5
5
  import {serializePath} from "./watch.js";
6
6
  import delve from "../util/delve.js";
@@ -11,6 +11,7 @@ export {logGets, gets}
11
11
 
12
12
  let withinSet = 0;
13
13
 
14
+
14
15
  /**
15
16
  * Turn the props on obj into JavasCript properties that return Proxies when accessed.
16
17
  * If called more than once, return the already-converted object.
@@ -112,7 +113,6 @@ class ProxyHandler {
112
113
  else if (prop === 'map' && Array.isArray(obj)) {
113
114
 
114
115
  let ngm = NodeGroupManager.get(this.root);
115
- ngm.clearSubscribersIfNeeded();
116
116
 
117
117
  return callback => {
118
118
  let loopInfo;
@@ -291,7 +291,7 @@ class ProxyHandler {
291
291
  let obj2 = obj === this.root && this.props ? this.props : obj;
292
292
  let result = Reflect.get(obj2, prop);
293
293
 
294
- // This is read by watchFunction() which is called in NodeGroup.applyOneExpr().
294
+ // This is read by watchFunction() which is called in ExprPath.apply().
295
295
  // It's used to see what variables contribute to an expression.
296
296
  if (logGets)
297
297
  gets.push([this.root, ...this.path, prop]);
@@ -392,7 +392,7 @@ function getProxy(obj, ph, path, path2) {
392
392
 
393
393
  /**
394
394
  * Call a function and record which watched variables it accesess, storing their paths in pathToTemplates.
395
- * Used by NodeGroup.applyOneExpr().
395
+ * Used by ExprPath.apply().
396
396
  * TODO: only allow this to be called once per callback.
397
397
  * @param callback {function}
398
398
  * @param ngm {NodeGroupManager}
@@ -14,18 +14,28 @@ export default class MultiValueMap {
14
14
  set.add(value);
15
15
  }
16
16
 
17
+ isEmpty() {
18
+ for (let key in this.data)
19
+ return true;
20
+ return false;
21
+ }
22
+
17
23
  // Get all values for a key
18
24
  getAll(key) {
19
25
  return this.data[key] || [];
20
26
  }
21
27
 
22
- // Remove one value from a key, and return it
28
+ /**
29
+ * Remove one value from a key, and return it.
30
+ * @param key {string}
31
+ * @param val If specified, make sure we delete this specific value, if a key exists more than once.
32
+ * @returns {*} */
23
33
  delete(key, val=undefined) {
24
34
  // if (key === '["Html2",[[["Html3",["F1","A"]],["Html3",["F1","B"]]]]]')
25
35
  // debugger;
26
-
36
+
27
37
  let data = this.data;
28
- // The partialUpdate benchmark shows having this check first makes the function slightly faster.
38
+
29
39
  // if (!data.hasOwnProperty(key))
30
40
  // return undefined;
31
41
 
@@ -33,24 +43,25 @@ export default class MultiValueMap {
33
43
  let result;
34
44
  let set = data[key];
35
45
  if (!set) // slower than pre-check.
36
- return undefined;
46
+ return undefined;
37
47
 
38
- if (val !== undefined) {
39
- set.delete(val);
40
- result = val;
48
+ // Delete any value.
49
+ if (val === undefined) {
50
+ //result = set.values().next().value; // get first item from set.
51
+ [result] = set; // Does the same as above and seems to be about the same speed.
52
+ set.delete(result);
41
53
  }
42
54
 
43
- // Delete any value.
55
+ // Delete a specific value.
44
56
  else {
45
- result = set.values().next().value;
46
- // [result] = set; // Does the same as above. is about the same speed?
47
- set.delete(result);
57
+ set.delete(val);
58
+ result = val;
48
59
  }
49
60
 
50
61
  // TODO: Will this make it slower?
51
62
  if (set.size === 0)
52
63
  delete data[key];
53
-
64
+
54
65
  return result;
55
66
  }
56
67