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.
- package/benchmarks/naive/Solarite.min.js +4 -0
- package/benchmarks/naive/index.html +14 -0
- package/benchmarks/naive/main.js +339 -0
- package/benchmarks/naive/package-lock.json +13 -0
- package/benchmarks/naive/package.json +23 -0
- package/benchmarks/readme.md +48 -0
- package/build/build.bat +3 -2
- package/build/build.js +1 -1
- package/dist/Solarite-debug.js +1941 -2791
- package/dist/Solarite.js +1697 -2511
- package/dist/Solarite.min.js +3 -3
- package/dist/udomdiff-license.txt +18 -0
- package/docs/index.md +695 -235
- package/docs/js/Playground.js +1 -1
- package/docs/js/codemirror/codemirror6.js +3683 -3206
- package/docs/js/codemirror/themeSolarIce.js +2 -2
- package/docs/js/documentation.js +2 -2
- package/docs/js/ui/CodeEditor.js +183 -45
- package/docs/js/ui/FlexResizer.js +15 -5
- package/docs/js/util/Errors.js +11 -0
- package/docs/media/documentation.css +6 -3
- package/index.html +462 -26
- package/package.json +1 -1
- package/readme.md +11 -1
- package/src/solarite/ExprPath.js +422 -135
- package/src/solarite/Globals.js +53 -0
- package/src/solarite/NodeGroup.js +300 -388
- package/src/solarite/Shell.js +31 -33
- package/src/solarite/Solarite.js +17 -2
- package/src/solarite/Template.js +75 -25
- package/src/solarite/Util.js +131 -7
- package/src/solarite/createSolarite.js +32 -25
- package/src/solarite/getArg.js +12 -12
- package/src/solarite/hash.js +18 -35
- package/src/solarite/r.js +128 -118
- package/src/solarite/watch3.js +98 -0
- package/src/{solarite → unused}/NodeGroupManager.js +86 -224
- package/src/unused/onConnect.js +79 -0
- package/src/{solarite → unused}/watch.js +2 -2
- package/src/{solarite → unused}/watch2.js +4 -4
- package/src/{solarite → util}/MultiValueMap.js +23 -12
- package/src/util/WeakArray.js +33 -0
- package/tests/Solarite.test.js +1108 -166
- package/tests/Testimony.js +274 -67
- package/tests/index.html +6 -35
- package/tests/run.bat +2 -0
- package/tests/NodeGroup.test.js +0 -115
- 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
|
-
|
|
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
|
-
|
|
145
|
+
assert(success);
|
|
146
|
+
|
|
271
147
|
let success2 = this.nodeGroupsAvailable.delete(ng2.closeKey, ng2);
|
|
272
|
-
|
|
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
|
|
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
|
|
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,
|
|
251
|
+
getNodeGroup(template, exact=null, replaceMode=null, exactKey=null) {
|
|
252
|
+
exactKey = exactKey || getObjectHash(template);
|
|
369
253
|
|
|
370
|
-
|
|
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
|
|
377
|
-
|
|
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
|
-
|
|
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 =
|
|
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()
|
|
278
|
+
/*#IFDEV*/ng.verify();/*#ENDIF*/
|
|
405
279
|
/*#IFDEV*/this.incrementLogDepth(-1);/*#ENDIF*/
|
|
406
|
-
/*#IFDEV*/if(
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
|
|
427
|
-
this.nodeGroupsAvailable.add(ng.exactKey, ng);
|
|
428
|
-
else
|
|
429
|
-
this.nodeGroupsInUse.push(ng)
|
|
299
|
+
this.nodeGroupsInUse.push(ng)
|
|
430
300
|
|
|
431
|
-
/*#IFDEV*/if(
|
|
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
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
*
|
|
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
|
|
508
|
-
if (
|
|
509
|
-
ngm = new NodeGroupManager(
|
|
510
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
572
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
+
return undefined;
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
result =
|
|
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
|
|
55
|
+
// Delete a specific value.
|
|
44
56
|
else {
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|