solarite 0.7.0 → 0.8.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/dist/Solarite-debug.js +5193 -4114
- package/dist/Solarite.js +4951 -3878
- package/dist/Solarite.min.js +2 -2
- package/dist/jsx-dev-runtime.js +3 -0
- package/dist/jsx-runtime.js +86 -0
- package/package.json +11 -10
- package/readme.md +8 -8
- package/src/MappedList.js +34 -0
- package/src/MultiValueMap.js +4 -4
- package/src/NodeGroup.js +205 -119
- package/src/Path.js +52 -53
- package/src/PathToAttribValue.js +182 -101
- package/src/PathToAttribs.js +10 -18
- package/src/PathToComponent.js +43 -19
- package/src/PathToEvent.js +18 -12
- package/src/PathToKey.js +0 -6
- package/src/PathToNodes.js +695 -155
- package/src/RootNodeGroup.js +23 -30
- package/src/Selector.js +207 -0
- package/src/Shell.js +276 -117
- package/src/Solarite.d.ts +89 -13
- package/src/Solarite.js +38 -25
- package/src/Template.js +20 -7
- package/src/Util.js +75 -60
- package/src/assert.js +1 -1
- package/src/assignAttributes.js +20 -19
- package/src/h.js +64 -37
- package/src/jsx.js +4 -7
- package/src/toEl.js +1 -1
- package/src/HtmlParser.js +0 -91
- package/src/PathToComment.js +0 -8
package/src/NodeGroup.js
CHANGED
|
@@ -6,9 +6,54 @@ import Path from "./Path.js";
|
|
|
6
6
|
import Globals from './Globals.js';
|
|
7
7
|
import PathToComponent from "./PathToComponent.js";
|
|
8
8
|
import PathToNodes from "./PathToNodes.js";
|
|
9
|
+
import {ensureDelegatedDispatcher, delegatedRootKey} from "./PathToAttribValue.js";
|
|
9
10
|
|
|
10
11
|
/** @typedef {boolean|string|number|function|Object|Array|Date|Node|Template} Expr */
|
|
11
12
|
|
|
13
|
+
/** Stand-in Shell for text NodeGroups, which are never parsed from html. Its default field
|
|
14
|
+
* values (no components, no live properties, no single-expression paths) are exactly what the
|
|
15
|
+
* per-row code must see for a bare Text node, so ng.shell is never null. */
|
|
16
|
+
const textShell = new Shell();
|
|
17
|
+
|
|
18
|
+
// The Shell whose delegated dispatchers a root last registered, kept on the RootNodeGroup so
|
|
19
|
+
// that a run of rows checks one field instead of asking at every bound node. A Symbol rather
|
|
20
|
+
// than a declared field, since only root NodeGroups ever carry it and a declared field would
|
|
21
|
+
// cost a slot on every row. The delegation mode isn't part of it: it comes from the root's
|
|
22
|
+
// render options, which are fixed when the root is created.
|
|
23
|
+
const lastStampedShellKey = Symbol('solariteStampedShell');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Run a Shell's precomputed resolve program (see Shell.buildResolveProgram) into the shell's
|
|
27
|
+
* shared slots array, which the caller has already seeded with its starting node.
|
|
28
|
+
* Each node is reached with firstChild/nextSibling pointer walks instead of childNodes[index];
|
|
29
|
+
* the live NodeList indexing is markedly slower, and the indices are small (markers are
|
|
30
|
+
* elements, often the first child after whitespace stripping). A negative step count means the
|
|
31
|
+
* program reaches this node by walking forward from an earlier sibling's slot instead of from
|
|
32
|
+
* its parent.
|
|
33
|
+
* @param slots {Node[]} The shell's shared scratch array; slot 0 is the fragment.
|
|
34
|
+
* @param ops {int[]} Flat [parentSlot, childIndex] pairs in dependency order.
|
|
35
|
+
* @param i {int} Index of the first op pair to run; earlier pairs are pre-seeded by the caller.
|
|
36
|
+
* @param s {int} Slot that pair fills.
|
|
37
|
+
* @return {Node[]} slots, so callers can resolve and use it in one expression. */
|
|
38
|
+
function runResolveOps(slots, ops, i, s) {
|
|
39
|
+
for (; i<ops.length; i+=2, s++) {
|
|
40
|
+
let k = ops[i+1], node;
|
|
41
|
+
if (k < 0) {
|
|
42
|
+
node = slots[ops[i]];
|
|
43
|
+
do
|
|
44
|
+
node = node.nextSibling;
|
|
45
|
+
while (++k < 0);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
node = slots[ops[i]].firstChild;
|
|
49
|
+
for (; k>0; k--)
|
|
50
|
+
node = node.nextSibling;
|
|
51
|
+
}
|
|
52
|
+
slots[s] = node;
|
|
53
|
+
}
|
|
54
|
+
return slots;
|
|
55
|
+
}
|
|
56
|
+
|
|
12
57
|
/**
|
|
13
58
|
* A group of Nodes instantiated from a Shell, with Expr's filled in.
|
|
14
59
|
*
|
|
@@ -42,11 +87,11 @@ export default class NodeGroup {
|
|
|
42
87
|
* matched by PathToNodes.applyKeyed(). Undefined for unkeyed NodeGroups. */
|
|
43
88
|
key;
|
|
44
89
|
|
|
45
|
-
/** @type {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
90
|
+
/** @type {Shell} The Shell this NodeGroup was cloned from, so the per-row code can read
|
|
91
|
+
* hasComponentPaths/hasLivePropPaths/pathsSingleExpr and the stamp program off it instead
|
|
92
|
+
* of copying them onto every instance and re-looking the Shell up on every apply.
|
|
93
|
+
* Text NodeGroups get the shared empty textShell, which reports false for all of them. */
|
|
94
|
+
shell;
|
|
50
95
|
|
|
51
96
|
/** @type {boolean} True until applyExprs() finishes the first time.
|
|
52
97
|
* While true, ancestor node caches can't reference this NodeGroup's nodes, so they don't need invalidation. */
|
|
@@ -57,6 +102,11 @@ export default class NodeGroup {
|
|
|
57
102
|
* @type {Node[]} Cached result of getNodes() used only for improving performance.*/
|
|
58
103
|
nodesCache;
|
|
59
104
|
|
|
105
|
+
/** @type {?Node[]} Slot nodes resolved by the first rewriteStamp(); a stamped group's
|
|
106
|
+
* element structure never changes while it stays stampable, so they're reused on every
|
|
107
|
+
* later rewrite. Declared here so every NodeGroup keeps one monomorphic hidden class. */
|
|
108
|
+
stampSlotsCache = null;
|
|
109
|
+
|
|
60
110
|
/**
|
|
61
111
|
* A map between <style> Elements and their text content.
|
|
62
112
|
* This lets NodeGroup.updateStyles() see when the style text has changed.
|
|
@@ -78,7 +128,7 @@ export default class NodeGroup {
|
|
|
78
128
|
this.rootNg = parentPath?.parentNg?.rootNg || this;
|
|
79
129
|
this.parentPath = parentPath;
|
|
80
130
|
|
|
81
|
-
/*#
|
|
131
|
+
/*#IFDEBUG*/assert(this.rootNg);/*#ENDIF*/
|
|
82
132
|
this.template = template;
|
|
83
133
|
|
|
84
134
|
// JSX templates carry their list key on the Template (tagged templates instead set it via
|
|
@@ -89,24 +139,22 @@ export default class NodeGroup {
|
|
|
89
139
|
// If it's just a text node, skip a bunch of unnecessary steps.
|
|
90
140
|
// el can be an existing Text node to adopt, from PathToNodes' bare-text fast path.
|
|
91
141
|
if (template.isText) {
|
|
142
|
+
this.shell = textShell;
|
|
92
143
|
this.closeKey = template.getCloseKey();
|
|
93
144
|
this.startNode = this.endNode = el || Globals.doc.createTextNode(template.html[0]);
|
|
94
145
|
}
|
|
95
146
|
|
|
96
147
|
else {
|
|
97
148
|
// Get a cached version of the parsed and instantiated html, and Paths:
|
|
98
|
-
const shell = Shell.get(template.html, template.svgMode);
|
|
149
|
+
const shell = this.shell = Shell.get(template.html, template.svgMode);
|
|
99
150
|
|
|
100
151
|
// The shell caches the close key so each new template doesn't repeat the WeakMap lookup.
|
|
101
152
|
this.closeKey = shell.closeKey ??= template.getCloseKey();
|
|
102
153
|
|
|
103
|
-
this.hasComponentPaths = shell.hasComponentPaths;
|
|
104
|
-
this.pathsSingleExpr = shell.pathsSingleExpr;
|
|
105
|
-
|
|
106
154
|
// A lone root element is cloned directly, skipping a throwaway fragment wrapper.
|
|
107
155
|
// Only for child NodeGroups; RootNodeGroup's grafting expects a fragment.
|
|
108
156
|
if (shell.singleRoot && parentPath !== null) {
|
|
109
|
-
const clone = shell.
|
|
157
|
+
const clone = shell.docFrag.firstChild.cloneNode(true);
|
|
110
158
|
this.startNode = this.endNode = clone;
|
|
111
159
|
|
|
112
160
|
// Stampable shells skip path creation entirely; the first applyExprs() routes
|
|
@@ -115,7 +163,7 @@ export default class NodeGroup {
|
|
|
115
163
|
this.setPathsFromFragment(clone, shell, 0, true);
|
|
116
164
|
}
|
|
117
165
|
else {
|
|
118
|
-
const shellFragment = shell.
|
|
166
|
+
const shellFragment = shell.docFrag.cloneNode(true);
|
|
119
167
|
|
|
120
168
|
if (shellFragment.nodeType === 11) { // DocumentFragment
|
|
121
169
|
this.startNode = shellFragment.firstChild;
|
|
@@ -127,7 +175,7 @@ export default class NodeGroup {
|
|
|
127
175
|
}
|
|
128
176
|
}
|
|
129
177
|
|
|
130
|
-
//#
|
|
178
|
+
//#IFDEBUG
|
|
131
179
|
this.verify();
|
|
132
180
|
//#ENDIF
|
|
133
181
|
}
|
|
@@ -158,10 +206,14 @@ export default class NodeGroup {
|
|
|
158
206
|
* Dispatches expression handling to other functions depending on the path type.
|
|
159
207
|
* @param exprs {(*|*[]|function|Template)[]}
|
|
160
208
|
* @param includeNonComponents {boolean} False to only apply component paths,
|
|
161
|
-
* used when the non-component exprs are known to be unchanged.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
209
|
+
* used when the non-component exprs are known to be unchanged.
|
|
210
|
+
* @param lastExprs {?Expr[]} The expressions applied last time, when the caller has them.
|
|
211
|
+
* Paths that would provably do nothing with an unchanged expression are then skipped —
|
|
212
|
+
* see Path.skipIfSame. A root template's event bindings are the usual beneficiaries:
|
|
213
|
+
* they are the same handlers on every render, and re-binding them costs a call apiece. */
|
|
214
|
+
applyExprs(exprs, includeNonComponents=true, lastExprs=null) {
|
|
215
|
+
|
|
216
|
+
/*#IFDEBUG*/
|
|
165
217
|
this.verify();
|
|
166
218
|
/*#ENDIF*/
|
|
167
219
|
|
|
@@ -169,14 +221,18 @@ export default class NodeGroup {
|
|
|
169
221
|
|
|
170
222
|
// Fast path: every path consumes exactly one expression and none are components,
|
|
171
223
|
// so skip the bookkeeping that maps expressions to paths.
|
|
172
|
-
if (this.pathsSingleExpr) {
|
|
224
|
+
if (this.shell.pathsSingleExpr) {
|
|
173
225
|
if (includeNonComponents) {
|
|
174
226
|
if (paths === null) { // Created from a stampable shell; no paths yet.
|
|
175
227
|
this.applyStamp(exprs);
|
|
176
228
|
return;
|
|
177
229
|
}
|
|
178
|
-
for (let i = paths.length - 1; i >= 0; i--)
|
|
179
|
-
paths[i]
|
|
230
|
+
for (let i = paths.length - 1; i >= 0; i--) {
|
|
231
|
+
let path = paths[i];
|
|
232
|
+
if (lastExprs !== null && path.skipIfSame && lastExprs[i] === exprs[i])
|
|
233
|
+
continue;
|
|
234
|
+
path.applySingle(exprs[i]);
|
|
235
|
+
}
|
|
180
236
|
|
|
181
237
|
if (this.styles)
|
|
182
238
|
this.updateStyles();
|
|
@@ -203,7 +259,7 @@ export default class NodeGroup {
|
|
|
203
259
|
let exprIndex = exprs.length; // Update exprs at paths.
|
|
204
260
|
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
261
|
for (let i = paths.length - 1, path; path = paths[i]; i--) {
|
|
206
|
-
if (i===0 && path instanceof PathToComponent && path.nodeMarker === this.
|
|
262
|
+
if (i===0 && path instanceof PathToComponent && path.nodeMarker === this.getRootEl())
|
|
207
263
|
continue;
|
|
208
264
|
|
|
209
265
|
// Get the expressions associated with this path.
|
|
@@ -215,15 +271,15 @@ export default class NodeGroup {
|
|
|
215
271
|
// They use expressions from the paths that provide their attributes.
|
|
216
272
|
if (path instanceof PathToComponent) {
|
|
217
273
|
let attribExprs = pathExprs.slice(i+1, i+1 + path.attribPaths.length); // +1 b/c we move forward from the component path.
|
|
218
|
-
path.
|
|
274
|
+
path.applyAll(attribExprs);
|
|
219
275
|
}
|
|
220
276
|
else if (includeNonComponents)
|
|
221
|
-
path.
|
|
277
|
+
path.applyAll(pathExprs[i]);
|
|
222
278
|
}
|
|
223
279
|
|
|
224
280
|
// If there's leftover expressions, there's probably an issue with the Shell that created this NodeGroup,
|
|
225
281
|
// and the number of paths not matching.
|
|
226
|
-
/*#
|
|
282
|
+
/*#IFDEBUG*/
|
|
227
283
|
assert(exprIndex === 0);
|
|
228
284
|
/*#ENDIF*/
|
|
229
285
|
|
|
@@ -239,7 +295,7 @@ export default class NodeGroup {
|
|
|
239
295
|
}
|
|
240
296
|
this.firstApply = false;
|
|
241
297
|
|
|
242
|
-
/*#
|
|
298
|
+
/*#IFDEBUG*/
|
|
243
299
|
this.verify();
|
|
244
300
|
/*#ENDIF*/
|
|
245
301
|
}
|
|
@@ -251,8 +307,7 @@ export default class NodeGroup {
|
|
|
251
307
|
* falls back to materializing real paths and applying normally.
|
|
252
308
|
* @param exprs {Expr[]} */
|
|
253
309
|
applyStamp(exprs) {
|
|
254
|
-
let
|
|
255
|
-
let shell = Shell.get(template.html, template.svgMode);
|
|
310
|
+
let shell = this.shell;
|
|
256
311
|
|
|
257
312
|
// 1. Bail to real paths when any child-node expression isn't a primitive.
|
|
258
313
|
let nodesIdx = shell.nodesPathIdx;
|
|
@@ -268,27 +323,71 @@ export default class NodeGroup {
|
|
|
268
323
|
}
|
|
269
324
|
}
|
|
270
325
|
|
|
271
|
-
// 2. Resolve target nodes, then
|
|
326
|
+
// 2. Resolve target nodes, then run the shell's compiled stamp program: a flat
|
|
327
|
+
// opcode per path replaces per-path applySingle() dispatch (see Shell.stampOp).
|
|
272
328
|
let slots = this.resolveStampSlots(shell);
|
|
273
|
-
let
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
329
|
+
let ops = shell.stampOp, slotIdx = shell.stampSlot, aux = shell.stampAux;
|
|
330
|
+
let stampers = shell.stampPaths;
|
|
331
|
+
let rootNg = this.rootNg;
|
|
332
|
+
let root = rootNg.rootEl;
|
|
333
|
+
let opt = rootNg.renderOptions?.eventDelegation;
|
|
334
|
+
let delegateDoc = opt === 'document';
|
|
335
|
+
let delegateAll = opt === undefined || opt === true || delegateDoc;
|
|
336
|
+
|
|
337
|
+
// Register this shell's delegated dispatchers once for a whole run of rows. They live on
|
|
338
|
+
// the root, not on the bound nodes, so asking per node — as the general binding path has
|
|
339
|
+
// to — would be a call and a set lookup for every handler in the list.
|
|
340
|
+
let names = shell.stampEventNames;
|
|
341
|
+
if (names !== null && delegateAll && rootNg[lastStampedShellKey] !== shell) {
|
|
342
|
+
for (let k=0; k<names.length; k++)
|
|
343
|
+
ensureDelegatedDispatcher(root, names[k], delegateDoc);
|
|
344
|
+
rootNg[lastStampedShellKey] = shell;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
let firstApply = this.firstApply;
|
|
348
|
+
for (let i = ops.length - 1; i >= 0; i--) {
|
|
349
|
+
let v = exprs[i];
|
|
350
|
+
let o = ops[i];
|
|
351
|
+
|
|
352
|
+
// Whole-parent child text: the marker is the (freshly cloned, empty) only-child
|
|
353
|
+
// slot. Child exprs are primitive here (step 1 bailed otherwise).
|
|
354
|
+
if (o === 2) {
|
|
283
355
|
if (typeof v === 'number')
|
|
284
356
|
v += '';
|
|
285
|
-
|
|
286
|
-
|
|
357
|
+
slots[slotIdx[i]].textContent = v;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Delegatable event with a valid handler shape: write the node expandos
|
|
361
|
+
// directly, mirroring bindEvent()'s delegated branch. An event-name-array
|
|
362
|
+
// delegation option or an invalid value falls through to the generic stamper.
|
|
363
|
+
else if (o === 3 && delegateAll
|
|
364
|
+
&& (typeof v === 'function' || (Array.isArray(v) && typeof v[0] === 'function'))) {
|
|
365
|
+
let sp = aux[i];
|
|
366
|
+
let node = slots[slotIdx[i]];
|
|
367
|
+
node[sp.delegatedKey] = v;
|
|
368
|
+
node[delegatedRootKey] = root;
|
|
287
369
|
}
|
|
288
370
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
371
|
+
// A plain attribute on a freshly cloned row: the shell left it off, so an empty
|
|
372
|
+
// value means there is simply nothing to write, and any other string can go
|
|
373
|
+
// straight in without reading the attribute back first.
|
|
374
|
+
else if (o === 4 && firstApply && typeof v === 'string') {
|
|
375
|
+
if (v !== '')
|
|
376
|
+
slots[slotIdx[i]].setAttribute(aux[i], v);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// The list key never touches the DOM.
|
|
380
|
+
else if (o === 1)
|
|
381
|
+
this.key = v;
|
|
382
|
+
|
|
383
|
+
// Everything else (attributes, disabled delegation, odd values) goes through
|
|
384
|
+
// the shared stamper's full applySingle() semantics.
|
|
385
|
+
else {
|
|
386
|
+
let stamper = stampers[i];
|
|
387
|
+
stamper.nodeMarker = slots[slotIdx[i]];
|
|
388
|
+
stamper.parentNg = this;
|
|
389
|
+
stamper.applySingle(v);
|
|
390
|
+
}
|
|
292
391
|
}
|
|
293
392
|
|
|
294
393
|
this.nodesCache = null;
|
|
@@ -302,7 +401,7 @@ export default class NodeGroup {
|
|
|
302
401
|
* @return {boolean} False when a child-node expression isn't primitive; the caller
|
|
303
402
|
* must then materialize paths and apply normally. */
|
|
304
403
|
rewriteStamp(template) {
|
|
305
|
-
let shell =
|
|
404
|
+
let shell = this.shell;
|
|
306
405
|
let newExprs = template.exprs;
|
|
307
406
|
let nodesIdx = shell.nodesPathIdx;
|
|
308
407
|
for (let i=0; i<nodesIdx.length; i++) {
|
|
@@ -312,19 +411,29 @@ export default class NodeGroup {
|
|
|
312
411
|
}
|
|
313
412
|
|
|
314
413
|
let oldExprs = this.template.exprs;
|
|
315
|
-
let
|
|
316
|
-
let slots =
|
|
317
|
-
for (let i =
|
|
318
|
-
|
|
414
|
+
let stampers = shell.stampPaths, slotIdx = shell.stampSlot, flags = shell.stampFlags;
|
|
415
|
+
let slots = this.stampSlotsCache; // Nodes are resolved only if something actually changed, then cached.
|
|
416
|
+
for (let i = stampers.length - 1; i >= 0; i--) {
|
|
417
|
+
// Live HTML properties (checked etc., boolean-valued) are exempt from the
|
|
418
|
+
// unchanged-value skip: a user's click flips the DOM property underneath the cached
|
|
419
|
+
// expression, and applySingle() compares against the live node before writing.
|
|
420
|
+
// The identity test is inline because most expressions are unchanged, and reaching
|
|
421
|
+
// exprSame() only to be told so costs more than the comparison itself.
|
|
422
|
+
let oldExpr = oldExprs[i], newExpr = newExprs[i];
|
|
423
|
+
let flag = flags[i];
|
|
424
|
+
if ((oldExpr !== newExpr && !exprSame(oldExpr, newExpr))
|
|
425
|
+
|| ((flag & 1) && typeof newExpr === 'boolean')) {
|
|
426
|
+
// .slice() is required: resolveStampSlots returns the Shell's SHARED scratch
|
|
427
|
+
// array, which the next row's resolve would overwrite.
|
|
319
428
|
if (slots === null)
|
|
320
|
-
slots = this.resolveStampSlots(shell);
|
|
429
|
+
slots = this.stampSlotsCache = this.resolveStampSlots(shell).slice();
|
|
321
430
|
let stamper = stampers[i];
|
|
322
|
-
let marker = slots[
|
|
431
|
+
let marker = slots[slotIdx[i]]; // The flat slot array, so the Path isn't loaded.
|
|
323
432
|
|
|
324
433
|
// Fast path for a wholeParent text path whose child already exists (the common
|
|
325
434
|
// rewrite case): set its value directly, skipping applySingle's branching and
|
|
326
435
|
// textNode bookkeeping. exprSame above already proved it changed.
|
|
327
|
-
if (
|
|
436
|
+
if (flag & 2) {
|
|
328
437
|
let v = newExprs[i], tn = marker.firstChild;
|
|
329
438
|
if (typeof v === 'number')
|
|
330
439
|
v += '';
|
|
@@ -359,16 +468,10 @@ export default class NodeGroup {
|
|
|
359
468
|
* @return {Node[]} The shell's shared scratch slots array. */
|
|
360
469
|
resolveStampSlots(shell) {
|
|
361
470
|
let slots = shell.resolveSlots;
|
|
471
|
+
// A singleRoot shell's first op pair is always [0, 0], so slot 1 is the row's own root
|
|
472
|
+
// element and the program can start at the second pair.
|
|
362
473
|
slots[1] = this.startNode;
|
|
363
|
-
|
|
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;
|
|
474
|
+
return runResolveOps(slots, shell.resolveOps, 2, 2);
|
|
372
475
|
}
|
|
373
476
|
|
|
374
477
|
/**
|
|
@@ -378,17 +481,8 @@ export default class NodeGroup {
|
|
|
378
481
|
* @param shell {?Shell}
|
|
379
482
|
* @return {Path[]} */
|
|
380
483
|
materializePaths(shell=null) {
|
|
381
|
-
shell ??=
|
|
382
|
-
let
|
|
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
|
-
}
|
|
484
|
+
shell ??= this.shell;
|
|
485
|
+
let result = this.clonePathsFromSlots(shell, this.resolveStampSlots(shell));
|
|
392
486
|
|
|
393
487
|
// A wholeParent child-node path that stamped a primitive left exactly one Text child.
|
|
394
488
|
for (let idx of shell.nodesPathIdx) {
|
|
@@ -428,14 +522,8 @@ export default class NodeGroup {
|
|
|
428
522
|
/**
|
|
429
523
|
* Get the root element of the NodeGroup's RootNodeGroup.
|
|
430
524
|
* @returns {HTMLElement|DocumentFragment} */
|
|
431
|
-
|
|
432
|
-
return this.rootNg.
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* @returns {RootNodeGroup} */
|
|
437
|
-
getRootNodeGroup() {
|
|
438
|
-
return this.rootNg;
|
|
525
|
+
getRootEl() {
|
|
526
|
+
return this.rootNg.rootEl;
|
|
439
527
|
}
|
|
440
528
|
|
|
441
529
|
/**
|
|
@@ -446,9 +534,6 @@ export default class NodeGroup {
|
|
|
446
534
|
* @param isRootClone {boolean} True when fragment is a direct clone of a singleRoot
|
|
447
535
|
* shell's root element: it fills slot 1 itself and the first op pair is skipped. */
|
|
448
536
|
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
537
|
|
|
453
538
|
// Fast path: run the shell's precomputed resolve program (see Shell.buildResolveProgram).
|
|
454
539
|
// Each Path.clone() would walk childNodes from the fragment root to its target node,
|
|
@@ -460,37 +545,45 @@ export default class NodeGroup {
|
|
|
460
545
|
// attribPaths behavior; pathOffset!==0 (root grafting) also uses the fallback.
|
|
461
546
|
let ops = shell.resolveOps;
|
|
462
547
|
if (ops && startingPathDepth === 0) {
|
|
463
|
-
let slots
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
s = 2;
|
|
469
|
-
}
|
|
470
|
-
else
|
|
548
|
+
let slots;
|
|
549
|
+
if (isRootClone) // The root element is also this.startNode, so it seeds slot 1 itself.
|
|
550
|
+
slots = this.resolveStampSlots(shell);
|
|
551
|
+
else {
|
|
552
|
+
slots = shell.resolveSlots;
|
|
471
553
|
slots[0] = fragment;
|
|
472
|
-
|
|
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;
|
|
554
|
+
runResolveOps(slots, ops, 0, 1);
|
|
486
555
|
}
|
|
556
|
+
this.clonePathsFromSlots(shell, slots);
|
|
487
557
|
}
|
|
488
|
-
else
|
|
558
|
+
else {
|
|
559
|
+
let paths = shell.paths;
|
|
560
|
+
let pathLength = paths.length; // For faster iteration
|
|
561
|
+
let result = this.paths = new Array(pathLength);
|
|
489
562
|
for (let i=0; i<pathLength; i++) {
|
|
490
563
|
let path = paths[i].clone(fragment, startingPathDepth)
|
|
491
564
|
path.parentNg = this;
|
|
492
565
|
result[i] = path;
|
|
493
566
|
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Copy the shell's Paths onto this NodeGroup's own nodes, taking each path's marker and
|
|
572
|
+
* before-node from the slots the resolve program just filled.
|
|
573
|
+
* @param shell {Shell}
|
|
574
|
+
* @param slots {Node[]} The shell's shared scratch slots, already resolved.
|
|
575
|
+
* @return {Path[]} */
|
|
576
|
+
clonePathsFromSlots(shell, slots) {
|
|
577
|
+
let paths = shell.paths;
|
|
578
|
+
let pathLength = paths.length;
|
|
579
|
+
let result = this.paths = new Array(pathLength);
|
|
580
|
+
for (let i=0; i<pathLength; i++) {
|
|
581
|
+
let p = paths[i];
|
|
582
|
+
let path = p.cloneWithNodes(p.beforeSlot >= 0 ? slots[p.beforeSlot] : null, slots[p.markerSlot]);
|
|
583
|
+
path.parentNg = this;
|
|
584
|
+
result[i] = path;
|
|
585
|
+
}
|
|
586
|
+
return result;
|
|
494
587
|
}
|
|
495
588
|
|
|
496
589
|
updateStyles() {
|
|
@@ -498,7 +591,7 @@ export default class NodeGroup {
|
|
|
498
591
|
for (let [style, oldText] of this.styles) {
|
|
499
592
|
let newText = style.textContent;
|
|
500
593
|
if (oldText !== newText)
|
|
501
|
-
Util.bindStyles(style, this.
|
|
594
|
+
Util.bindStyles(style, this.rootNg.rootEl);
|
|
502
595
|
}
|
|
503
596
|
}
|
|
504
597
|
|
|
@@ -508,16 +601,14 @@ export default class NodeGroup {
|
|
|
508
601
|
* @param pathOffset {int} */
|
|
509
602
|
activateEmbeds(root, shell, pathOffset=0) {
|
|
510
603
|
|
|
511
|
-
let rootEl = this.rootNg.
|
|
604
|
+
let rootEl = this.rootNg.rootEl;
|
|
512
605
|
if (rootEl) {
|
|
513
|
-
let options = this.rootNg.
|
|
606
|
+
let options = this.rootNg.renderOptions;
|
|
514
607
|
|
|
515
608
|
// ids
|
|
516
609
|
if (options?.ids !== false) {
|
|
517
610
|
for (let path of shell.ids) {
|
|
518
|
-
|
|
519
|
-
path = path.slice(0, -pathOffset);
|
|
520
|
-
let el = Path.resolve(root, path);
|
|
611
|
+
let el = Path.resolve(root, path, pathOffset);
|
|
521
612
|
Util.bindId(rootEl, el);
|
|
522
613
|
}
|
|
523
614
|
}
|
|
@@ -527,11 +618,8 @@ export default class NodeGroup {
|
|
|
527
618
|
if (shell.styles.length)
|
|
528
619
|
this.styles = new Map();
|
|
529
620
|
for (let path of shell.styles) {
|
|
530
|
-
if (pathOffset)
|
|
531
|
-
path = path.slice(0, -pathOffset);
|
|
532
|
-
|
|
533
621
|
/** @type {HTMLStyleElement} */
|
|
534
|
-
let style = Path.resolve(root, path);
|
|
622
|
+
let style = Path.resolve(root, path, pathOffset);
|
|
535
623
|
if (rootEl.nodeType === 1) {
|
|
536
624
|
Util.bindStyles(style, rootEl);
|
|
537
625
|
this.styles.set(style, style.textContent);
|
|
@@ -542,9 +630,7 @@ export default class NodeGroup {
|
|
|
542
630
|
// scripts
|
|
543
631
|
if (options?.scripts !== false) {
|
|
544
632
|
for (let path of shell.scripts) {
|
|
545
|
-
|
|
546
|
-
path = path.slice(0, -pathOffset);
|
|
547
|
-
let script = Path.resolve(root, path);
|
|
633
|
+
let script = Path.resolve(root, path, pathOffset);
|
|
548
634
|
// Indirect eval runs in global scope (correct for a <script> tag) and, unlike a direct
|
|
549
635
|
// eval, doesn't force terser to keep every top-level name in the bundle unmangled.
|
|
550
636
|
(0, eval)(script.textContent)
|
|
@@ -553,7 +639,7 @@ export default class NodeGroup {
|
|
|
553
639
|
}
|
|
554
640
|
}
|
|
555
641
|
|
|
556
|
-
//#
|
|
642
|
+
//#IFDEBUG
|
|
557
643
|
getParentNode() {
|
|
558
644
|
return this.startNode?.parentNode
|
|
559
645
|
}
|