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/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 {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;
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
- /*#IFDEV*/assert(this.rootNg);/*#ENDIF*/
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.fragment.firstChild.cloneNode(true);
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.fragment.cloneNode(true);
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
- //#IFDEV
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
- applyExprs(exprs, includeNonComponents=true) {
163
-
164
- /*#IFDEV*/
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].applySingle(exprs[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.getRootNode())
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.apply(attribExprs);
274
+ path.applyAll(attribExprs);
219
275
  }
220
276
  else if (includeNonComponents)
221
- path.apply(pathExprs[i]);
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
- /*#IFDEV*/
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
- /*#IFDEV*/
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 template = this.template;
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 write each expression.
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 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];
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
- marker.textContent = v;
286
- continue;
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
- stamper.nodeMarker = marker;
290
- stamper.parentNg = this;
291
- stamper.applySingle(exprs[i]);
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 = Shell.get(template.html, template.svgMode);
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 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])) {
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[paths[i].markerSlot];
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 (stamper.wholeParent) {
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
- 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;
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 ??= 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
- }
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
- getRootNode() {
432
- return this.rootNg.root;
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 = 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
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
- // 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;
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.getRootNodeGroup().root);
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.root;
604
+ let rootEl = this.rootNg.rootEl;
512
605
  if (rootEl) {
513
- let options = this.rootNg.options;
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
- if (pathOffset)
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
- if (pathOffset)
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
- //#IFDEV
642
+ //#IFDEBUG
557
643
  getParentNode() {
558
644
  return this.startNode?.parentNode
559
645
  }