solarite 0.5.2 → 0.7.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.
- package/LICENSE +21 -0
- package/dist/Solarite-debug.js +4847 -3878
- package/dist/Solarite.js +4583 -3626
- package/dist/Solarite.min.js +2 -4
- package/package.json +19 -6
- package/readme.md +58 -11
- package/src/Globals.js +54 -72
- package/src/HtmlParser.js +90 -90
- package/src/MultiValueMap.js +57 -105
- package/src/NodeGroup.js +624 -470
- package/src/Path.js +224 -211
- package/src/PathToAttribValue.js +401 -261
- package/src/PathToAttribs.js +113 -80
- package/src/PathToComment.js +7 -7
- package/src/PathToComponent.js +183 -188
- package/src/PathToEvent.js +76 -64
- package/src/PathToKey.js +19 -0
- package/src/PathToNodes.js +1053 -566
- package/src/RootNodeGroup.js +120 -8
- package/src/Shell.js +570 -348
- package/src/Solarite.d.ts +134 -113
- package/src/Solarite.js +243 -286
- package/src/Template.js +195 -274
- package/src/Util.js +353 -351
- package/src/assert.js +10 -10
- package/src/assignAttributes.js +63 -0
- package/src/delve.js +55 -43
- package/src/h.js +220 -138
- package/src/jsx-dev-runtime.d.ts +1 -0
- package/src/jsx-dev-runtime.js +5 -0
- package/src/jsx-runtime.d.ts +21 -0
- package/src/jsx-runtime.js +84 -0
- package/src/jsx.js +194 -0
- package/src/toEl.js +77 -82
- package/dist/udomdiff-license.txt +0 -18
- package/src/getArg.js +0 -137
- package/src/hash.js +0 -89
- package/src/udomdiff.js +0 -176
- package/src/unused/FastLookupArray.js +0 -54
- package/src/unused/Hashes.js +0 -339
- package/src/unused/InUse.test.js +0 -92
- package/src/unused/InUseMap.js +0 -98
- package/src/unused/LinkedList.js +0 -117
- package/src/unused/LinkedList.test.js +0 -115
- package/src/unused/Misc.js +0 -13
- package/src/unused/Perf.js +0 -47
- package/src/unused/TrackedArray.js +0 -54
- package/src/unused/WeakArray.js +0 -33
- package/src/watch.js +0 -543
package/src/NodeGroup.js
CHANGED
|
@@ -1,470 +1,624 @@
|
|
|
1
|
-
import assert from "./assert.js";
|
|
2
|
-
import Util, {flattenAndIndent, nodeToArrayTree, setIndent} from "./Util.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import Path from "./Path.js";
|
|
6
|
-
import Globals from './Globals.js';
|
|
7
|
-
import PathToComponent from "./PathToComponent.js";
|
|
8
|
-
import PathToNodes from "./PathToNodes.js";
|
|
9
|
-
|
|
10
|
-
/** @typedef {boolean|string|number|function|Object|Array|Date|Node|Template} Expr */
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A group of Nodes instantiated from a Shell, with Expr's filled in.
|
|
14
|
-
*
|
|
15
|
-
* The range is determined by startNode and nodeMarker.
|
|
16
|
-
* startNode - never null. An empty text node is created before the first path if none exists.
|
|
17
|
-
* nodeMarker - null if this Nodegroup is at the end of its parents' nodes.*/
|
|
18
|
-
export default class NodeGroup {
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @Type {RootNodeGroup} */
|
|
22
|
-
rootNg;
|
|
23
|
-
|
|
24
|
-
/** @type {Path} */
|
|
25
|
-
parentPath;
|
|
26
|
-
|
|
27
|
-
/** @type {Node|HTMLElement} First node of NodeGroup. Should never be null. */
|
|
28
|
-
startNode;
|
|
29
|
-
|
|
30
|
-
/** @type {Node|HTMLElement} A node that never changes that this NodeGroup should always insert its nodes before.
|
|
31
|
-
* An empty text node will be created to insertBefore if there's no other NodeMarker and this isn't at the last position.
|
|
32
|
-
* TODO: But sometimes startNode and endNode point to the same node. Document this inconsistency. */
|
|
33
|
-
endNode;
|
|
34
|
-
|
|
35
|
-
/** @type {Path[]} */
|
|
36
|
-
paths =
|
|
37
|
-
|
|
38
|
-
/** @type {string} Key that matches the template
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/** @type {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
* @
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
this.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
1
|
+
import assert from "./assert.js";
|
|
2
|
+
import Util, {flattenAndIndent, nodeToArrayTree, setIndent} from "./Util.js";
|
|
3
|
+
import {exprSame} from "./Template.js";
|
|
4
|
+
import Shell from "./Shell.js";
|
|
5
|
+
import Path from "./Path.js";
|
|
6
|
+
import Globals from './Globals.js';
|
|
7
|
+
import PathToComponent from "./PathToComponent.js";
|
|
8
|
+
import PathToNodes from "./PathToNodes.js";
|
|
9
|
+
|
|
10
|
+
/** @typedef {boolean|string|number|function|Object|Array|Date|Node|Template} Expr */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A group of Nodes instantiated from a Shell, with Expr's filled in.
|
|
14
|
+
*
|
|
15
|
+
* The range is determined by startNode and nodeMarker.
|
|
16
|
+
* startNode - never null. An empty text node is created before the first path if none exists.
|
|
17
|
+
* nodeMarker - null if this Nodegroup is at the end of its parents' nodes.*/
|
|
18
|
+
export default class NodeGroup {
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @Type {RootNodeGroup} */
|
|
22
|
+
rootNg;
|
|
23
|
+
|
|
24
|
+
/** @type {Path} */
|
|
25
|
+
parentPath;
|
|
26
|
+
|
|
27
|
+
/** @type {Node|HTMLElement} First node of NodeGroup. Should never be null. */
|
|
28
|
+
startNode;
|
|
29
|
+
|
|
30
|
+
/** @type {Node|HTMLElement} A node that never changes that this NodeGroup should always insert its nodes before.
|
|
31
|
+
* An empty text node will be created to insertBefore if there's no other NodeMarker and this isn't at the last position.
|
|
32
|
+
* TODO: But sometimes startNode and endNode point to the same node. Document this inconsistency. */
|
|
33
|
+
endNode;
|
|
34
|
+
|
|
35
|
+
/** @type {?Path[]} Null for text NodeGroups; created by setPathsFromFragment(). */
|
|
36
|
+
paths = null;
|
|
37
|
+
|
|
38
|
+
/** @type {string} Key that only matches the template. */
|
|
39
|
+
closeKey;
|
|
40
|
+
|
|
41
|
+
/** @type {*} List key from the template's key=${} expression; written by PathToKey,
|
|
42
|
+
* matched by PathToNodes.applyKeyed(). Undefined for unkeyed NodeGroups. */
|
|
43
|
+
key;
|
|
44
|
+
|
|
45
|
+
/** @type {boolean} True if any of this NodeGroup's own paths is a PathToComponent. */
|
|
46
|
+
hasComponentPaths = false;
|
|
47
|
+
|
|
48
|
+
/** @type {boolean} True if every path consumes exactly one expression and none are components. */
|
|
49
|
+
pathsSingleExpr = false;
|
|
50
|
+
|
|
51
|
+
/** @type {boolean} True until applyExprs() finishes the first time.
|
|
52
|
+
* While true, ancestor node caches can't reference this NodeGroup's nodes, so they don't need invalidation. */
|
|
53
|
+
firstApply = true;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @internal
|
|
57
|
+
* @type {Node[]} Cached result of getNodes() used only for improving performance.*/
|
|
58
|
+
nodesCache;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A map between <style> Elements and their text content.
|
|
62
|
+
* This lets NodeGroup.updateStyles() see when the style text has changed.
|
|
63
|
+
* @type {?Map<HTMLStyleElement, string>} */
|
|
64
|
+
styles;
|
|
65
|
+
|
|
66
|
+
/** @type {Template} */
|
|
67
|
+
template;
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create an "instantiated" NodeGroup from a Template and add it to an element.
|
|
72
|
+
* Don't call applyExprs() yet to apply expressions or instantiate components yet.
|
|
73
|
+
* @param template {Template} Create it from the html strings and expressions in this template.
|
|
74
|
+
* @param parentPath {?Path}
|
|
75
|
+
* @param el {?HTMLElement} Optional, pre-existing htmlElement that will be the root.
|
|
76
|
+
* @param options {?object} Only used for RootNodeGroup */
|
|
77
|
+
constructor(template, parentPath=null, el=null, options=null) {
|
|
78
|
+
this.rootNg = parentPath?.parentNg?.rootNg || this;
|
|
79
|
+
this.parentPath = parentPath;
|
|
80
|
+
|
|
81
|
+
/*#IFDEV*/assert(this.rootNg);/*#ENDIF*/
|
|
82
|
+
this.template = template;
|
|
83
|
+
|
|
84
|
+
// JSX templates carry their list key on the Template (tagged templates instead set it via
|
|
85
|
+
// a PathToKey during applyExprs). Adopt it so the keyed reconciler sees ng.key uniformly.
|
|
86
|
+
if (template.key !== undefined)
|
|
87
|
+
this.key = template.key;
|
|
88
|
+
|
|
89
|
+
// If it's just a text node, skip a bunch of unnecessary steps.
|
|
90
|
+
// el can be an existing Text node to adopt, from PathToNodes' bare-text fast path.
|
|
91
|
+
if (template.isText) {
|
|
92
|
+
this.closeKey = template.getCloseKey();
|
|
93
|
+
this.startNode = this.endNode = el || Globals.doc.createTextNode(template.html[0]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
else {
|
|
97
|
+
// Get a cached version of the parsed and instantiated html, and Paths:
|
|
98
|
+
const shell = Shell.get(template.html, template.svgMode);
|
|
99
|
+
|
|
100
|
+
// The shell caches the close key so each new template doesn't repeat the WeakMap lookup.
|
|
101
|
+
this.closeKey = shell.closeKey ??= template.getCloseKey();
|
|
102
|
+
|
|
103
|
+
this.hasComponentPaths = shell.hasComponentPaths;
|
|
104
|
+
this.pathsSingleExpr = shell.pathsSingleExpr;
|
|
105
|
+
|
|
106
|
+
// A lone root element is cloned directly, skipping a throwaway fragment wrapper.
|
|
107
|
+
// Only for child NodeGroups; RootNodeGroup's grafting expects a fragment.
|
|
108
|
+
if (shell.singleRoot && parentPath !== null) {
|
|
109
|
+
const clone = shell.fragment.firstChild.cloneNode(true);
|
|
110
|
+
this.startNode = this.endNode = clone;
|
|
111
|
+
|
|
112
|
+
// Stampable shells skip path creation entirely; the first applyExprs() routes
|
|
113
|
+
// to applyStamp(), and paths are materialized only if the group is rewritten.
|
|
114
|
+
if (shell.stampable !== true)
|
|
115
|
+
this.setPathsFromFragment(clone, shell, 0, true);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const shellFragment = shell.fragment.cloneNode(true);
|
|
119
|
+
|
|
120
|
+
if (shellFragment.nodeType === 11) { // DocumentFragment
|
|
121
|
+
this.startNode = shellFragment.firstChild;
|
|
122
|
+
this.endNode = shellFragment.lastChild;
|
|
123
|
+
} else
|
|
124
|
+
this.startNode = this.endNode = shellFragment;
|
|
125
|
+
|
|
126
|
+
this.instantiate(shell, shellFragment, el, options);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//#IFDEV
|
|
131
|
+
this.verify();
|
|
132
|
+
//#ENDIF
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Set up paths and embeds from the cloned fragment.
|
|
137
|
+
* RootNodeGroup overrides this with its more involved setup.
|
|
138
|
+
* @param shell {Shell}
|
|
139
|
+
* @param shellFragment {DocumentFragment|HTMLElement|Text}
|
|
140
|
+
* @param el {?HTMLElement} Unused here; used by RootNodeGroup.
|
|
141
|
+
* @param options {?object} Unused here; used by RootNodeGroup. */
|
|
142
|
+
instantiate(shell, shellFragment, el, options) {
|
|
143
|
+
// A non-stampable group must keep a non-null paths array; null is the stamped/text
|
|
144
|
+
// sentinel, and reuse would otherwise route a path-less group through rewriteStamp(),
|
|
145
|
+
// which only exists for stampable shells. Zero-expression shells have no paths to build.
|
|
146
|
+
if (shell.paths.length)
|
|
147
|
+
this.setPathsFromFragment(shellFragment, shell);
|
|
148
|
+
else
|
|
149
|
+
this.paths = [];
|
|
150
|
+
|
|
151
|
+
if (shell.hasEmbeds)
|
|
152
|
+
this.activateEmbeds(shellFragment, shell);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Use the paths to insert the given expressions.
|
|
158
|
+
* Dispatches expression handling to other functions depending on the path type.
|
|
159
|
+
* @param exprs {(*|*[]|function|Template)[]}
|
|
160
|
+
* @param includeNonComponents {boolean} False to only apply component paths,
|
|
161
|
+
* used when the non-component exprs are known to be unchanged. */
|
|
162
|
+
applyExprs(exprs, includeNonComponents=true) {
|
|
163
|
+
|
|
164
|
+
/*#IFDEV*/
|
|
165
|
+
this.verify();
|
|
166
|
+
/*#ENDIF*/
|
|
167
|
+
|
|
168
|
+
let paths = this.paths;
|
|
169
|
+
|
|
170
|
+
// Fast path: every path consumes exactly one expression and none are components,
|
|
171
|
+
// so skip the bookkeeping that maps expressions to paths.
|
|
172
|
+
if (this.pathsSingleExpr) {
|
|
173
|
+
if (includeNonComponents) {
|
|
174
|
+
if (paths === null) { // Created from a stampable shell; no paths yet.
|
|
175
|
+
this.applyStamp(exprs);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
for (let i = paths.length - 1; i >= 0; i--)
|
|
179
|
+
paths[i].applySingle(exprs[i]);
|
|
180
|
+
|
|
181
|
+
if (this.styles)
|
|
182
|
+
this.updateStyles();
|
|
183
|
+
|
|
184
|
+
// Invalidate the nodes cache because we just changed it.
|
|
185
|
+
this.nodesCache = null;
|
|
186
|
+
}
|
|
187
|
+
this.firstApply = false;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!paths) { // Text NodeGroups have no paths.
|
|
192
|
+
this.firstApply = false;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Things to consider:
|
|
197
|
+
// 1. Paths consume a varying number of expressions.
|
|
198
|
+
// An PathToAttribs may use multipe expressions. E.g. <div class="${1} ${2}">
|
|
199
|
+
// While an PathToComponent uses zero.
|
|
200
|
+
// 2. An PathToComponent references other Paths that set its attribute values.
|
|
201
|
+
// 3. We apply them in reverse order so that a <select> box has its children created from an expression
|
|
202
|
+
// before its instantiated and its value attribute is set via an expression.
|
|
203
|
+
let exprIndex = exprs.length; // Update exprs at paths.
|
|
204
|
+
let pathExprs = new Array(paths.length); // Store all the expressions that map to a single path. Only paths to attribute values can have more than one.
|
|
205
|
+
for (let i = paths.length - 1, path; path = paths[i]; i--) {
|
|
206
|
+
if (i===0 && path instanceof PathToComponent && path.nodeMarker === this.getRootNode())
|
|
207
|
+
continue;
|
|
208
|
+
|
|
209
|
+
// Get the expressions associated with this path.
|
|
210
|
+
let exprCount = path.getExpressionCount();
|
|
211
|
+
pathExprs[i] = exprs.slice(exprIndex-exprCount, exprIndex); // slice() probably doesn't allocate if the JS vm implements copy on write.
|
|
212
|
+
exprIndex -= exprCount;
|
|
213
|
+
|
|
214
|
+
// Component expressions don't have a corresponding user-provided expression.
|
|
215
|
+
// They use expressions from the paths that provide their attributes.
|
|
216
|
+
if (path instanceof PathToComponent) {
|
|
217
|
+
let attribExprs = pathExprs.slice(i+1, i+1 + path.attribPaths.length); // +1 b/c we move forward from the component path.
|
|
218
|
+
path.apply(attribExprs);
|
|
219
|
+
}
|
|
220
|
+
else if (includeNonComponents)
|
|
221
|
+
path.apply(pathExprs[i]);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// If there's leftover expressions, there's probably an issue with the Shell that created this NodeGroup,
|
|
225
|
+
// and the number of paths not matching.
|
|
226
|
+
/*#IFDEV*/
|
|
227
|
+
assert(exprIndex === 0);
|
|
228
|
+
/*#ENDIF*/
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
if (includeNonComponents) {
|
|
232
|
+
|
|
233
|
+
// TODO: Only do this if we have Paths within styles?
|
|
234
|
+
this.updateStyles();
|
|
235
|
+
|
|
236
|
+
// Invalidate the nodes cache because we just changed it.
|
|
237
|
+
this.nodesCache = null;
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
this.firstApply = false;
|
|
241
|
+
|
|
242
|
+
/*#IFDEV*/
|
|
243
|
+
this.verify();
|
|
244
|
+
/*#ENDIF*/
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Write expressions into a freshly stamped (or pooled path-less) NodeGroup through the
|
|
249
|
+
* shell's shared stamper paths, allocating no per-instance Path objects.
|
|
250
|
+
* Child-node expressions must be primitives (one text write each); anything else
|
|
251
|
+
* falls back to materializing real paths and applying normally.
|
|
252
|
+
* @param exprs {Expr[]} */
|
|
253
|
+
applyStamp(exprs) {
|
|
254
|
+
let template = this.template;
|
|
255
|
+
let shell = Shell.get(template.html, template.svgMode);
|
|
256
|
+
|
|
257
|
+
// 1. Bail to real paths when any child-node expression isn't a primitive.
|
|
258
|
+
let nodesIdx = shell.nodesPathIdx;
|
|
259
|
+
for (let i=0; i<nodesIdx.length; i++) {
|
|
260
|
+
let t = typeof exprs[nodesIdx[i]];
|
|
261
|
+
if (t !== 'string' && t !== 'number') {
|
|
262
|
+
let paths = this.materializePaths(shell);
|
|
263
|
+
for (let i = paths.length - 1; i >= 0; i--)
|
|
264
|
+
paths[i].applySingle(exprs[i]);
|
|
265
|
+
this.nodesCache = null;
|
|
266
|
+
this.firstApply = false;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 2. Resolve target nodes, then write each expression.
|
|
272
|
+
let slots = this.resolveStampSlots(shell);
|
|
273
|
+
let paths = shell.paths, stampers = shell.stampPaths;
|
|
274
|
+
for (let i = paths.length - 1; i >= 0; i--) {
|
|
275
|
+
let stamper = stampers[i];
|
|
276
|
+
let marker = slots[paths[i].markerSlot];
|
|
277
|
+
|
|
278
|
+
// A wholeParent text path's marker is the (freshly cloned, empty) only-child slot:
|
|
279
|
+
// write its text directly, skipping applySingle's branching and the shared-stamper
|
|
280
|
+
// bookkeeping. Child exprs are primitive here (step 1 bailed otherwise).
|
|
281
|
+
if (stamper.wholeParent) {
|
|
282
|
+
let v = exprs[i];
|
|
283
|
+
if (typeof v === 'number')
|
|
284
|
+
v += '';
|
|
285
|
+
marker.textContent = v;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
stamper.nodeMarker = marker;
|
|
290
|
+
stamper.parentNg = this;
|
|
291
|
+
stamper.applySingle(exprs[i]);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
this.nodesCache = null;
|
|
295
|
+
this.firstApply = false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* In-place rewrite of a stamped (path-less) NodeGroup through the shared stampers,
|
|
300
|
+
* comparing expressions and writing only the changed ones. The group stays path-less.
|
|
301
|
+
* @param template {Template} The new template; the caller assigns it to this.template.
|
|
302
|
+
* @return {boolean} False when a child-node expression isn't primitive; the caller
|
|
303
|
+
* must then materialize paths and apply normally. */
|
|
304
|
+
rewriteStamp(template) {
|
|
305
|
+
let shell = Shell.get(template.html, template.svgMode);
|
|
306
|
+
let newExprs = template.exprs;
|
|
307
|
+
let nodesIdx = shell.nodesPathIdx;
|
|
308
|
+
for (let i=0; i<nodesIdx.length; i++) {
|
|
309
|
+
let t = typeof newExprs[nodesIdx[i]];
|
|
310
|
+
if (t !== 'string' && t !== 'number')
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let oldExprs = this.template.exprs;
|
|
315
|
+
let paths = shell.paths, stampers = shell.stampPaths;
|
|
316
|
+
let slots = null; // Nodes are resolved only if something actually changed.
|
|
317
|
+
for (let i = paths.length - 1; i >= 0; i--) {
|
|
318
|
+
if (!exprSame(oldExprs[i], newExprs[i])) {
|
|
319
|
+
if (slots === null)
|
|
320
|
+
slots = this.resolveStampSlots(shell);
|
|
321
|
+
let stamper = stampers[i];
|
|
322
|
+
let marker = slots[paths[i].markerSlot];
|
|
323
|
+
|
|
324
|
+
// Fast path for a wholeParent text path whose child already exists (the common
|
|
325
|
+
// rewrite case): set its value directly, skipping applySingle's branching and
|
|
326
|
+
// textNode bookkeeping. exprSame above already proved it changed.
|
|
327
|
+
if (stamper.wholeParent) {
|
|
328
|
+
let v = newExprs[i], tn = marker.firstChild;
|
|
329
|
+
if (typeof v === 'number')
|
|
330
|
+
v += '';
|
|
331
|
+
if (tn !== null && tn.nodeType === 3 && tn === marker.lastChild)
|
|
332
|
+
tn.nodeValue = v;
|
|
333
|
+
else {
|
|
334
|
+
// Empty/absent text child: fall back to the stamper, then clear its per-row
|
|
335
|
+
// state immediately so the shared stamper doesn't carry into the next row.
|
|
336
|
+
stamper.nodeMarker = marker;
|
|
337
|
+
stamper.parentNg = this;
|
|
338
|
+
stamper.applySingle(newExprs[i]);
|
|
339
|
+
stamper.textNode = null;
|
|
340
|
+
stamper.textValue = null;
|
|
341
|
+
stamper.nodesCache = null;
|
|
342
|
+
}
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
stamper.nodeMarker = marker;
|
|
347
|
+
stamper.parentNg = this;
|
|
348
|
+
stamper.applySingle(newExprs[i]);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Run the shell's resolve program from this NodeGroup's root element.
|
|
357
|
+
* Only valid for singleRoot shells, whose ops always start with the root's own pair.
|
|
358
|
+
* @param shell {Shell}
|
|
359
|
+
* @return {Node[]} The shell's shared scratch slots array. */
|
|
360
|
+
resolveStampSlots(shell) {
|
|
361
|
+
let slots = shell.resolveSlots;
|
|
362
|
+
slots[1] = this.startNode;
|
|
363
|
+
let ops = shell.resolveOps;
|
|
364
|
+
// firstChild/nextSibling pointer walk; see setPathsFromFragment for why not childNodes[i].
|
|
365
|
+
for (let i=2, s=2; i<ops.length; i+=2, s++) {
|
|
366
|
+
let node = slots[ops[i]].firstChild;
|
|
367
|
+
for (let k=ops[i+1]; k>0; k--)
|
|
368
|
+
node = node.nextSibling;
|
|
369
|
+
slots[s] = node;
|
|
370
|
+
}
|
|
371
|
+
return slots;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Create the real Path objects for a NodeGroup that was created by applyStamp().
|
|
376
|
+
* Called lazily, the first time the group is rewritten in place.
|
|
377
|
+
* Recovers the bare-text state of child-node paths that stamped a primitive.
|
|
378
|
+
* @param shell {?Shell}
|
|
379
|
+
* @return {Path[]} */
|
|
380
|
+
materializePaths(shell=null) {
|
|
381
|
+
shell ??= Shell.get(this.template.html, this.template.svgMode);
|
|
382
|
+
let slots = this.resolveStampSlots(shell);
|
|
383
|
+
let paths = shell.paths;
|
|
384
|
+
let pathLength = paths.length;
|
|
385
|
+
let result = this.paths = new Array(pathLength);
|
|
386
|
+
for (let i=0; i<pathLength; i++) {
|
|
387
|
+
let p = paths[i];
|
|
388
|
+
let path = p.cloneWithNodes(p.beforeSlot >= 0 ? slots[p.beforeSlot] : null, slots[p.markerSlot]);
|
|
389
|
+
path.parentNg = this;
|
|
390
|
+
result[i] = path;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// A wholeParent child-node path that stamped a primitive left exactly one Text child.
|
|
394
|
+
for (let idx of shell.nodesPathIdx) {
|
|
395
|
+
let path = result[idx];
|
|
396
|
+
let tn = path.nodeMarker.firstChild;
|
|
397
|
+
if (tn !== null && tn.nodeType === 3 && tn === path.nodeMarker.lastChild) {
|
|
398
|
+
path.textNode = tn;
|
|
399
|
+
path.textValue = tn.nodeValue;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return result;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get all the nodes inclusive between startNode and endNode.
|
|
407
|
+
* TODO: when not using nodesCache, could this use less memory with yield?
|
|
408
|
+
* But we'd need to save the reference to the next Node in case it's removed.
|
|
409
|
+
* @return {(Node|HTMLElement)[]} */
|
|
410
|
+
getNodes() {
|
|
411
|
+
// applyExprs() invalidates this cache.
|
|
412
|
+
let result = this.nodesCache;
|
|
413
|
+
if (result) // This does speed up the partialUpdate benchmark by 10-15%.
|
|
414
|
+
return result;
|
|
415
|
+
|
|
416
|
+
result = [];
|
|
417
|
+
let current = this.startNode
|
|
418
|
+
let afterLast = this.endNode?.nextSibling
|
|
419
|
+
while (current && current !== afterLast) {
|
|
420
|
+
result.push(current)
|
|
421
|
+
current = current.nextSibling
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
this.nodesCache = result;
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Get the root element of the NodeGroup's RootNodeGroup.
|
|
430
|
+
* @returns {HTMLElement|DocumentFragment} */
|
|
431
|
+
getRootNode() {
|
|
432
|
+
return this.rootNg.root;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* @returns {RootNodeGroup} */
|
|
437
|
+
getRootNodeGroup() {
|
|
438
|
+
return this.rootNg;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Copy paths in fragment to this.paths.
|
|
443
|
+
* @param fragment {DocumentFragment|HTMLElement}
|
|
444
|
+
* @param shell {Shell}
|
|
445
|
+
* @param startingPathDepth {int}
|
|
446
|
+
* @param isRootClone {boolean} True when fragment is a direct clone of a singleRoot
|
|
447
|
+
* shell's root element: it fills slot 1 itself and the first op pair is skipped. */
|
|
448
|
+
setPathsFromFragment(fragment, shell, startingPathDepth=0, isRootClone=false) {
|
|
449
|
+
let paths = shell.paths;
|
|
450
|
+
let pathLength = paths.length; // For faster iteration
|
|
451
|
+
let result = this.paths = new Array(pathLength);
|
|
452
|
+
|
|
453
|
+
// Fast path: run the shell's precomputed resolve program (see Shell.buildResolveProgram).
|
|
454
|
+
// Each Path.clone() would walk childNodes from the fragment root to its target node,
|
|
455
|
+
// re-traversing the same ancestors for every path. The program instead resolves each
|
|
456
|
+
// unique node exactly once into the slots array: ops is flat [parentSlot, childIndex]
|
|
457
|
+
// pairs in dependency order, pair i filling slot i+1, with slot 0 being the fragment.
|
|
458
|
+
// Paths then copy themselves via cloneWithNodes() using their precomputed slot indexes.
|
|
459
|
+
// Only built for component-free shells, since PathToComponent.clone() has special
|
|
460
|
+
// attribPaths behavior; pathOffset!==0 (root grafting) also uses the fallback.
|
|
461
|
+
let ops = shell.resolveOps;
|
|
462
|
+
if (ops && startingPathDepth === 0) {
|
|
463
|
+
let slots = shell.resolveSlots;
|
|
464
|
+
let i = 0, s = 1;
|
|
465
|
+
if (isRootClone) { // Slot 1 is the root element itself; skip its op pair.
|
|
466
|
+
slots[1] = fragment;
|
|
467
|
+
i = 2;
|
|
468
|
+
s = 2;
|
|
469
|
+
}
|
|
470
|
+
else
|
|
471
|
+
slots[0] = fragment;
|
|
472
|
+
// Resolve each node via firstChild/nextSibling pointer walks instead of
|
|
473
|
+
// childNodes[index]; the live NodeList indexing is markedly slower, and indices
|
|
474
|
+
// are small (markers are elements, often the first child after whitespace stripping).
|
|
475
|
+
for (; i<ops.length; i+=2, s++) {
|
|
476
|
+
let node = slots[ops[i]].firstChild;
|
|
477
|
+
for (let k=ops[i+1]; k>0; k--)
|
|
478
|
+
node = node.nextSibling;
|
|
479
|
+
slots[s] = node;
|
|
480
|
+
}
|
|
481
|
+
for (let i=0; i<pathLength; i++) {
|
|
482
|
+
let p = paths[i];
|
|
483
|
+
let path = p.cloneWithNodes(p.beforeSlot >= 0 ? slots[p.beforeSlot] : null, slots[p.markerSlot]);
|
|
484
|
+
path.parentNg = this;
|
|
485
|
+
result[i] = path;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else
|
|
489
|
+
for (let i=0; i<pathLength; i++) {
|
|
490
|
+
let path = paths[i].clone(fragment, startingPathDepth)
|
|
491
|
+
path.parentNg = this;
|
|
492
|
+
result[i] = path;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
updateStyles() {
|
|
497
|
+
if (this.styles)
|
|
498
|
+
for (let [style, oldText] of this.styles) {
|
|
499
|
+
let newText = style.textContent;
|
|
500
|
+
if (oldText !== newText)
|
|
501
|
+
Util.bindStyles(style, this.getRootNodeGroup().root);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* @param root {HTMLElement|DocumentFragment}
|
|
507
|
+
* @param shell {Shell}
|
|
508
|
+
* @param pathOffset {int} */
|
|
509
|
+
activateEmbeds(root, shell, pathOffset=0) {
|
|
510
|
+
|
|
511
|
+
let rootEl = this.rootNg.root;
|
|
512
|
+
if (rootEl) {
|
|
513
|
+
let options = this.rootNg.options;
|
|
514
|
+
|
|
515
|
+
// ids
|
|
516
|
+
if (options?.ids !== false) {
|
|
517
|
+
for (let path of shell.ids) {
|
|
518
|
+
if (pathOffset)
|
|
519
|
+
path = path.slice(0, -pathOffset);
|
|
520
|
+
let el = Path.resolve(root, path);
|
|
521
|
+
Util.bindId(rootEl, el);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// styles
|
|
526
|
+
if (options?.styles !== false) {
|
|
527
|
+
if (shell.styles.length)
|
|
528
|
+
this.styles = new Map();
|
|
529
|
+
for (let path of shell.styles) {
|
|
530
|
+
if (pathOffset)
|
|
531
|
+
path = path.slice(0, -pathOffset);
|
|
532
|
+
|
|
533
|
+
/** @type {HTMLStyleElement} */
|
|
534
|
+
let style = Path.resolve(root, path);
|
|
535
|
+
if (rootEl.nodeType === 1) {
|
|
536
|
+
Util.bindStyles(style, rootEl);
|
|
537
|
+
this.styles.set(style, style.textContent);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
}
|
|
542
|
+
// scripts
|
|
543
|
+
if (options?.scripts !== false) {
|
|
544
|
+
for (let path of shell.scripts) {
|
|
545
|
+
if (pathOffset)
|
|
546
|
+
path = path.slice(0, -pathOffset);
|
|
547
|
+
let script = Path.resolve(root, path);
|
|
548
|
+
// Indirect eval runs in global scope (correct for a <script> tag) and, unlike a direct
|
|
549
|
+
// eval, doesn't force terser to keep every top-level name in the bundle unmangled.
|
|
550
|
+
(0, eval)(script.textContent)
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
//#IFDEV
|
|
557
|
+
getParentNode() {
|
|
558
|
+
return this.startNode?.parentNode
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
get debug() {
|
|
562
|
+
return [
|
|
563
|
+
`parentNode: ${this.parentNode?.tagName?.toLowerCase()}`,
|
|
564
|
+
'nodes:',
|
|
565
|
+
...setIndent(this.getNodes().map(item => {
|
|
566
|
+
if (item?.nodeType) {
|
|
567
|
+
|
|
568
|
+
let tree = nodeToArrayTree(item, nextNode => {
|
|
569
|
+
|
|
570
|
+
let path = this.paths.find(path=>(path instanceof PathToNodes) && path.getNodes().includes(nextNode));
|
|
571
|
+
if (path)
|
|
572
|
+
return [`Path.nodes:`]
|
|
573
|
+
|
|
574
|
+
return [];
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
// TODO: How to indend nodes belonging to a path vs those that just occur after the path?
|
|
578
|
+
return flattenAndIndent(tree)
|
|
579
|
+
}
|
|
580
|
+
else if (item instanceof Path)
|
|
581
|
+
return setIndent(item.debug, 1)
|
|
582
|
+
}).flat(), 1)
|
|
583
|
+
]
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
get debugNodes() { return this.getNodes() }
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
get debugNodesHtml() { return this.getNodes().map(n => n.outerHTML || n.textContent) }
|
|
590
|
+
|
|
591
|
+
verify() {
|
|
592
|
+
if (!window.verify)
|
|
593
|
+
return;
|
|
594
|
+
|
|
595
|
+
assert(this.startNode)
|
|
596
|
+
assert(this.endNode)
|
|
597
|
+
//assert(this.startNode !== this.endNode) // This can be true.
|
|
598
|
+
assert(this.startNode.parentNode === this.endNode.parentNode)
|
|
599
|
+
|
|
600
|
+
// Only if connected:
|
|
601
|
+
assert(!this.startNode.parentNode || this.startNode === this.endNode || this.startNode.compareDocumentPosition(this.endNode) === Node.DOCUMENT_POSITION_FOLLOWING)
|
|
602
|
+
|
|
603
|
+
// if (this.parentPath)
|
|
604
|
+
// assert(this.parentPath.nodeGroups.includes(this));
|
|
605
|
+
|
|
606
|
+
for (let path of this.paths || []) {
|
|
607
|
+
assert(path.parentNg === this)
|
|
608
|
+
|
|
609
|
+
// Fails for detached NodeGroups.
|
|
610
|
+
// NodeGroups get detached when their nodes are removed by reconcileNodes()
|
|
611
|
+
let parentNode = this.getParentNode();
|
|
612
|
+
if (parentNode)
|
|
613
|
+
assert(this.getParentNode().contains(path.getParentNode()))
|
|
614
|
+
path.verify();
|
|
615
|
+
// TODO: Make sure path nodes are all within our own node range.
|
|
616
|
+
}
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
//#ENDIF
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
|