solarite 0.1.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 (63) hide show
  1. package/build/build.bat +3 -0
  2. package/build/build.js +139 -0
  3. package/build/lib/rollup.min.js +11 -0
  4. package/build/lib/source-map.min.js +1 -0
  5. package/build/lib/terser.min.js +1 -0
  6. package/dist/Solarite-debug.js +4143 -0
  7. package/dist/Solarite.js +3740 -0
  8. package/dist/Solarite.min.js +4 -0
  9. package/docs/index.md +423 -0
  10. package/docs/js/Playground.js +184 -0
  11. package/docs/js/codemirror/codemirror6.js +32036 -0
  12. package/docs/js/codemirror/themeSolarIce.js +312 -0
  13. package/docs/js/documentation.js +32 -0
  14. package/docs/js/ui/CodeEditor.js +840 -0
  15. package/docs/js/ui/DarkToggle.js +52 -0
  16. package/docs/js/ui/FlexResizer.js +142 -0
  17. package/docs/js/util/Draggable2.js +151 -0
  18. package/docs/js/util/Errors.js +9 -0
  19. package/docs/js/util/Html.js +147 -0
  20. package/docs/js/util/Icons.js +623 -0
  21. package/docs/js/util/Input.js +253 -0
  22. package/docs/js/util/Util.js +88 -0
  23. package/docs/js/util/delve.js +43 -0
  24. package/docs/media/FiraCode400.woff2 +0 -0
  25. package/docs/media/cabin-latin-700.woff2 +0 -0
  26. package/docs/media/documentation.css +93 -0
  27. package/docs/media/eternium.css +1123 -0
  28. package/docs/media/solarite-machine.webp +0 -0
  29. package/index.html +325 -0
  30. package/package.json +33 -0
  31. package/readme.md +3 -0
  32. package/src/solarite/ExprPath.js +554 -0
  33. package/src/solarite/MultiValueMap.js +65 -0
  34. package/src/solarite/NodeGroup.js +706 -0
  35. package/src/solarite/NodeGroupManager.js +582 -0
  36. package/src/solarite/Shell.js +307 -0
  37. package/src/solarite/Solarite.js +19 -0
  38. package/src/solarite/Template.js +85 -0
  39. package/src/solarite/Util.js +264 -0
  40. package/src/solarite/createSolarite.js +267 -0
  41. package/src/solarite/getArg.js +99 -0
  42. package/src/solarite/hash.js +101 -0
  43. package/src/solarite/r.js +143 -0
  44. package/src/solarite/udomdiff.js +233 -0
  45. package/src/solarite/watch.js +302 -0
  46. package/src/solarite/watch2.js +439 -0
  47. package/src/unused/FastLookupArray.js +54 -0
  48. package/src/unused/Hashes.js +339 -0
  49. package/src/unused/InUse.test.js +92 -0
  50. package/src/unused/InUseMap.js +98 -0
  51. package/src/unused/LinkedList.js +117 -0
  52. package/src/unused/LinkedList.test.js +115 -0
  53. package/src/unused/Perf.js +47 -0
  54. package/src/unused/Template.js +108 -0
  55. package/src/util/Errors.js +9 -0
  56. package/src/util/Util.js +88 -0
  57. package/src/util/delve.js +43 -0
  58. package/tests/Benchmark.test.js +319 -0
  59. package/tests/NodeGroup.test.js +115 -0
  60. package/tests/Shell.test.js +75 -0
  61. package/tests/Solarite.test.js +2896 -0
  62. package/tests/Testimony.js +602 -0
  63. package/tests/index.html +75 -0
@@ -0,0 +1,582 @@
1
+ import MultiValueMap from "./MultiValueMap.js";
2
+ import NodeGroup from "./NodeGroup.js";
3
+ import {getObjectHash} from "./hash.js";
4
+ import {serializePath} from "./watch.js";
5
+
6
+ import {assert} from "../util/Errors.js";
7
+
8
+
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
+
23
+ /**
24
+ * Manage all the NodeGroups for a single WebComponent or root HTMLElement
25
+ * There's one NodeGroup for the root of the WebComponent, and one for every ${...} expression that creates Node children.
26
+ * And each NodeGroup manages the one or more nodes created by the expression.
27
+ *
28
+ * An instance of this class exists for each element that r() renders to. */
29
+ export default class NodeGroupManager {
30
+
31
+ /** @type {HTMLElement|DocumentFragment} */
32
+ rootEl;
33
+
34
+ /** @type {NodeGroup} */
35
+ rootNg;
36
+
37
+ /** @type {Change[]} */
38
+ changes = [];
39
+
40
+
41
+
42
+ //#IFDEV
43
+ modifications;
44
+ logDepth=0
45
+ //#ENDIF
46
+
47
+ /**
48
+ * A map from the html strings and exprs that created a node group, to the NodeGroup.
49
+ * 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
+ * @type {MultiValueMap<string, (string|Template)[], NodeGroup>} */
52
+ nodeGroupsAvailable = new MultiValueMap();
53
+ nodeGroupsInUse = [];
54
+
55
+
56
+ /** @type {RenderOptions} */
57
+ options = {};
58
+
59
+
60
+ //#IFDEV
61
+ mutationWatcher;
62
+ mutationWatcherEnabled = true;
63
+ //#ENDIF
64
+
65
+ /**
66
+ * @param rootEl {HTMLElement|DocumentFragment} If not specified, the first element of the html will be the rootEl. */
67
+ constructor(rootEl=null) {
68
+ this.rootEl = rootEl;
69
+ /*
70
+ //#IFDEV
71
+
72
+ function closestCustomElement(node) {
73
+ do {
74
+ if (node.tagName && node.tagName.includes('-'))
75
+ return node;
76
+ } while (node = node.parentNode);
77
+ }
78
+
79
+ // TODO: Only trigger if we modify nodes inside an ExprPath.
80
+ // TODO: Enable this even when not in dev mode, because it's so useful for debugging?
81
+ // But it modifies the top level prototypes.
82
+ // TODO: Remove the onBeforeMutation callback when this.rootEl is not in the document.
83
+ // Because we won't get notified of document changes then anyway.
84
+ if (this.rootEl && this.rootEl.ownerDocument?.defaultView) { // TODO: Bind whenever we have rootEl.
85
+ this.mutationWatcher = MutationWatcher.getFromDocument(this.rootEl.ownerDocument);
86
+ this.mutationWatcher.onBeforeMutation.push((node, action, args) => {
87
+
88
+ // If a modification was made
89
+ if (this.mutationWatcherEnabled) {
90
+ if (this.rootEl.contains(node) && closestCustomElement(node) === this.rootEl) {
91
+ //console.log(node, action, args);
92
+ //throw new Error('DOM modification');
93
+ }
94
+
95
+ // If another DOM node steals one of ours by adding it to itself.
96
+ // TODO: append can use multiple arguments.
97
+ if (['insertBefore', 'append', 'appendChild'].includes(action)
98
+ && this.rootEl.contains(args[0]) && closestCustomElement(args[0]) === this.rootEl) {
99
+
100
+ //console.log(node, action, args);
101
+ //throw new Error('Another element attempted to steal one of our nodes.');
102
+ }
103
+ }
104
+ });
105
+ }
106
+ //#ENDIF
107
+ */
108
+ }
109
+
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
+
231
+ /**
232
+ *
233
+ * 1. Delete a NodeGroup from this.nodeGroupsAvailable that matches this exactKey.
234
+ * 2. Then delete all of that NodeGroup's parents' exactKey entries
235
+ * We don't move them to in-use because we plucked the NodeGroup from them, they no longer match their exactKeys.
236
+ * 3. Then we move all the NodeGroup's exact+close keyed children to inUse because we don't want future calls
237
+ * to getNodeGroup() to borrow the children now that the whole NodeGroup is in-use.
238
+ *
239
+ * TODO: Have NodeGroups keep track of whether they're inUse.
240
+ * That way when we go up or down we don't have to remove those with .inUse===true
241
+ *
242
+ * @param exactKey
243
+ * @param goUp
244
+ * @param child
245
+ * @returns {?NodeGroup} */
246
+ findAndDeleteExact(exactKey, goUp=true, child=undefined) {
247
+
248
+ let ng = this.nodeGroupsAvailable.delete(exactKey, child);
249
+ if (ng) {
250
+ /*#IFDEV*/assert(ng.exactKey === exactKey);/*#ENDIF*/
251
+
252
+ // Mark close-key version as in-use.
253
+ let closeNg = this.nodeGroupsAvailable.delete(ng.closeKey, ng);
254
+ /*#IFDEV*/assert(closeNg);/*#ENDIF*/
255
+
256
+ // Mark our self as in-use.
257
+ this.nodeGroupsInUse.push(ng)
258
+
259
+ ng.inUse = true;
260
+ closeNg.inUse = true;
261
+
262
+ // Mark all parents that have this NodeGroup as a child as in-use.
263
+ // So that way we don't use this parent again
264
+ if (goUp) {
265
+ let ng2 = ng;
266
+ while (ng2 = ng2?.parentPath?.parentNg) {
267
+ if (!ng2.inUse) {
268
+ ng2.inUse = true;
269
+ let success = this.nodeGroupsAvailable.delete(ng2.exactKey, ng2);
270
+ // assert(success);
271
+ let success2 = this.nodeGroupsAvailable.delete(ng2.closeKey, ng2);
272
+ // assert(success);
273
+ /*#IFDEV*/assert(success === success2)/*#ENDIF*/
274
+
275
+ // console.log(getHtml(ng2))
276
+ if (success) {
277
+ this.nodeGroupsInUse.push(ng2)
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ // Recurse to mark all child NodeGroups as in-use.
284
+ for (let path of ng.paths)
285
+ for (let childNg of path.nodeGroups) {
286
+ if (!childNg.inUse)
287
+ this.findAndDeleteExact(childNg.exactKey, false, childNg);
288
+ childNg.inUse = true;
289
+ }
290
+
291
+ if (ng.parentPath) {
292
+ //ng.parentPath.clearNodesCache();
293
+ // ng.parentPath = null;
294
+ //ng.parentPath.removeNodeGroup(ng);
295
+ }
296
+
297
+ return ng;
298
+ }
299
+ return null;
300
+ }
301
+
302
+ /**
303
+ * @param closeKey {string}
304
+ * @param exactKey {string}
305
+ * @param goUp {boolean}
306
+ * @returns {NodeGroup} */
307
+ findAndDeleteClose(closeKey, exactKey, goUp=true) {
308
+ let ng = this.nodeGroupsAvailable.delete(closeKey);
309
+ if (ng) {
310
+
311
+ // We matched on a new key, so delete the old exactKey.
312
+ let exactNg = this.nodeGroupsAvailable.delete(ng.exactKey, ng);
313
+
314
+ /*#IFDEV*/assert(exactNg);/*#ENDIF*/
315
+ /*#IFDEV*/assert(ng === exactNg)/*#ENDIF*/
316
+
317
+
318
+ ng.inUse = true;
319
+ if (goUp) {
320
+ let ng2 = ng;
321
+
322
+ // We borrowed a node from another node group so make sure its parent isn't still an exact match.
323
+ while (ng2 = ng2?.parentPath?.parentNg) {
324
+ if (!ng2.inUse) {
325
+ ng2.inUse = true; // Might speed it up slightly?
326
+ let success = this.nodeGroupsAvailable.delete(ng2.exactKey, ng2);
327
+ /*#IFDEV*/assert(success);/*#ENDIF*/
328
+
329
+ // But it can still be a close match, so we don't use this code.
330
+ success = this.nodeGroupsAvailable.delete(ng2.closeKey, ng2);
331
+ /*#IFDEV*/assert(success);/*#ENDIF*/
332
+ }
333
+ }
334
+ }
335
+
336
+ // Recursively mark all child NodeGroups as in-use.
337
+ // We actually DON't want to do this becuse applyExprs is going to swap out the child NodeGroups
338
+ // and mark them as in-use as it goes.
339
+ // that's probably why uncommenting this causes tests to fail.
340
+ // for (let path of ng.paths)
341
+ // for (let childNg of path.nodeGroups)
342
+ // this.findAndDeleteExact(childNg.exactKey, false, childNg);
343
+
344
+
345
+ ng.exactKey = exactKey;
346
+ ng.closeKey = closeKey;
347
+ this.nodeGroupsInUse.push(ng)
348
+
349
+
350
+ if (ng.parentPath) {
351
+ //ng.parentPath.clearNodesCache();
352
+ //ng.parentPath = null;
353
+ //ng.parentPath.removeNodeGroup(ng);
354
+ }
355
+ }
356
+
357
+
358
+ return ng;
359
+ }
360
+
361
+ /**
362
+ * Get an existing or create a new NodeGroup that matches the template,
363
+ * but don't reparent it if it's somewhere else.
364
+ * @param template {Template}
365
+ * @param exact {?boolean}
366
+ * @param createForWatch
367
+ * @return {?NodeGroup} */
368
+ getNodeGroup(template, exact=null, createForWatch=false) {
369
+
370
+ let exactKey = getObjectHash(template)
371
+
372
+ /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Looking for ${exact ? 'exact' : 'close'} match: ` + template.debug)/*#ENDIF*/
373
+
374
+ // 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*/
384
+ }
385
+
386
+ // 2. Try to find a close match.
387
+ else {
388
+ // We don't need to delete the exact match bc it's already been deleted in the prev pass.
389
+ let closeKey = template.getCloseKey();
390
+ ng = createForWatch ? null : this.findAndDeleteClose(closeKey, exactKey);
391
+
392
+ // 2. Update expression values if they've changed.
393
+ 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*/
400
+ /*#IFDEV*/this.incrementLogDepth(1);/*#ENDIF*/
401
+ /*#IFDEV*/ng.verify();/*#ENDIF*/
402
+ ng.applyExprs(template.exprs);
403
+
404
+ /*#IFDEV*/ng.verify()/*#ENDIF*/
405
+ /*#IFDEV*/this.incrementLogDepth(-1);/*#ENDIF*/
406
+ /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Updated close to: ` + ng.debug)/*#ENDIF*/
407
+ }
408
+
409
+ // 3. Or if not found, create a new NodeGroup
410
+ else {
411
+ /*#IFDEV*/this.incrementLogDepth(1);/*#ENDIF*/
412
+ ng = new NodeGroup(template, this);
413
+ /*#IFDEV*/this.incrementLogDepth(-1);/*#ENDIF*/
414
+
415
+ //#IFDEV
416
+ this.modifications.created.push(...ng.getNodes())
417
+ //#ENDIF
418
+
419
+
420
+ // 4. Mark NodeGroup as being in-use.
421
+ // TODO: Moving from one group to another thrashes the gc. Is there a faster way?
422
+ // Could I have just a single WeakSet of those in use?
423
+ // Perhaps also result could cache its last exprKey and then we'd use only one map?
424
+ ng.exactKey = exactKey;
425
+ 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)
430
+
431
+ /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Created new ` + ng.debug)/*#ENDIF*/
432
+ }
433
+ }
434
+
435
+ // New!
436
+ // We clear the parent PathExpr's nodesCache when we remove ourselves from it.
437
+ // Benchmarking shows this doesn't slow down the partialUpdate benchmark.
438
+ if (ng.parentPath) {
439
+ // ng.parentPath.clearNodesCache(); // Makes partialUpdate benchmark 10x slower!
440
+ ng.parentPath = null;
441
+ }
442
+
443
+
444
+ /*#IFDEV*/ng.verify()/*#ENDIF*/
445
+
446
+ return ng;
447
+ }
448
+
449
+ reset() {
450
+ //this.changes = [];
451
+ let available = this.nodeGroupsAvailable
452
+ for (let ng of this.nodeGroupsInUse) {
453
+ ng.inUse = false;
454
+ available.add(ng.exactKey, ng)
455
+ available.add(ng.closeKey, ng)
456
+ }
457
+ this.nodeGroupsInUse = [];
458
+
459
+ // Used for watches
460
+ this.changes = [];
461
+
462
+ /*#IFDEV*/this.log('----------------------')/*#ENDIF*/
463
+ // TODO: free the memory from any nodeGroupsAvailable() after render is done, since they weren't used?
464
+ }
465
+
466
+
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()
478
+ }
479
+
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
+ }
499
+ }
500
+
501
+
502
+ /**
503
+ * Get the NodeGroupManager for a Web Component.
504
+ * @param rootEl {Solarite|HTMLElement}
505
+ * @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);
511
+ }
512
+
513
+ return ngm;
514
+ }
515
+
516
+
517
+ //#IFDEV
518
+ static logEnabled = false;
519
+ incrementLogDepth(level) {
520
+ this.logDepth += level;
521
+ }
522
+ log(msg, level=0) {
523
+ this.logDepth += level;
524
+ if (NodeGroupManager.logEnabled) {
525
+ let indent = ' '.repeat(this.logDepth);
526
+ console.log(indent + msg);
527
+ }
528
+ }
529
+
530
+ /**
531
+ * @returns {NodeGroup[]} */
532
+ getAllAvailableGroups() {
533
+ let result = new Set();
534
+ for (let values of Object.values(this.nodeGroupsAvailable.data))
535
+ result.add(...values)
536
+ return [...result];
537
+ }
538
+
539
+ verify() {
540
+ if (!window.verify)
541
+ return;
542
+
543
+
544
+ let findCloseMatch = item => {
545
+ let names = this.nodeGroupsAvailable.hasValue(item);
546
+ for (let name of names)
547
+ if (name.startsWith('@'))
548
+ return true;
549
+ return false;
550
+ }
551
+
552
+ // Check to make sure every exact match is also in close matches.
553
+ for (let name in this.nodeGroupsAvailable)
554
+ if (!name.startsWith('@)'))
555
+ for (let item of this.nodeGroupsAvailable.getAll(name))
556
+ assert(findCloseMatch(item))
557
+
558
+ // Recursively traverse through all node Groups
559
+ if (this.rootNg)
560
+ this.rootNg.verify();
561
+
562
+ for (let ng of this.getAllAvailableGroups())
563
+ ng.verify();
564
+ }
565
+ //#ENDIF
566
+ }
567
+
568
+ NodeGroupManager.pendingChildren = [];
569
+
570
+ /**
571
+ * Each Element that has Expr children has an associated NodeGroupManager here.
572
+ * @type {WeakMap<HTMLElement, NodeGroupManager>} */
573
+ let nodeGroupManagers = new WeakMap();
574
+
575
+
576
+
577
+ export class LoopInfo {
578
+ constructor(loopTemplate, itemTransformer) {
579
+ this.template = loopTemplate
580
+ this.itemTransformer = itemTransformer;
581
+ }
582
+ }