solarite 0.3.1 → 0.4.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
@@ -1,11 +1,10 @@
1
1
  import {assert} from "./assert.js";
2
- import ExprPath, {ExprPathType, resolveNodePath} from "./ExprPath.js";
3
- import {getObjectHash} from "./hash.js";
4
- import Shell from "./Shell.js";
5
2
  import Util, {flattenAndIndent, nodeToArrayTree, setIndent} from "./Util.js";
6
- //import NodeGroupManager from "./NodeGroupManager.js";
7
3
  import delve from "./delve.js";
8
- import Globals from "./Globals.js";
4
+ import Shell from "./Shell.js";
5
+ import RootNodeGroup from './RootNodeGroup.js';
6
+ import ExprPath, {ExprPathType, resolveNodePath} from "./ExprPath.js";
7
+ import Globals from './Globals.js';
9
8
 
10
9
  /** @typedef {boolean|string|number|function|Object|Array|Date|Node|Template} Expr */
11
10
 
@@ -55,30 +54,39 @@ export default class NodeGroup {
55
54
  * @type {?Map<HTMLStyleElement, string>} */
56
55
  styles;
57
56
 
57
+ dynamicComponents = new Set();
58
+ staticComponents = [];
59
+
60
+ /** @type {Template} */
61
+ template;
62
+
58
63
 
59
64
  /**
60
65
  * Create an "instantiated" NodeGroup from a Template and add it to an element.
61
66
  * @param template {Template} Create it from the html strings and expressions in this template.
62
67
  * @param parentPath {?ExprPath} */
63
68
  constructor(template, parentPath=null) {
69
+ this.rootNg = parentPath?.parentNg?.rootNg || this;
70
+ this.parentPath = parentPath;
71
+
64
72
  if (!(this instanceof RootNodeGroup)) {
65
73
 
66
- let [fragment, shell] = this.init(template, parentPath);
74
+ let [fragment, shell] = this.populateFromTemplate(template);
67
75
 
68
76
  if (fragment && template.exprs.length) {
69
77
  this.updatePaths(fragment, shell.paths);
70
78
 
71
79
  // Static web components can sometimes have children created via expressions.
72
80
  // But calling applyExprs() will mess up the shell's path to them.
73
- // So we find them first, then call activateStaticComponents() after their children have been created.
74
- let staticComponents = this.findStaticComponents(fragment, shell);
81
+ // So we find them first, then call instantiateStaticComponents() after their children have been created.
82
+ this.staticComponents = this.findStaticComponents(fragment, shell);
75
83
 
76
84
  this.activateEmbeds(fragment, shell);
77
85
 
78
86
  // Apply exprs
79
87
  this.applyExprs(template.exprs);
80
88
 
81
- this.instantiateStaticComponents(staticComponents);
89
+ this.instantiateStaticComponents(this.staticComponents);
82
90
  }
83
91
  else if (shell)
84
92
  this.activateEmbeds(fragment, shell);
@@ -89,47 +97,33 @@ export default class NodeGroup {
89
97
  * Common init shared by RootNodeGroup and NodeGroup constructors.
90
98
  * But in a separate function because they need to do this at a different step.
91
99
  * @param template {Template} Create it from the html strings and expressions in this template.
92
- * @param parentPath {?ExprPath}
93
- * @param exactKey {?string} Optional, if already calculated.
94
- * @param closeKey {?string}
95
- * @returns {[DocumentFragment, Shell]} */
96
- init(template, parentPath=null, exactKey=null, closeKey=null) {
97
- this.exactKey = exactKey || template.getExactKey();
98
- this.closeKey = closeKey || template.getCloseKey();
99
-
100
- this.parentPath = parentPath;
101
- this.rootNg = parentPath?.parentNg?.rootNg || this;
102
-
100
+ * @returns {[DocumentFragment, Shell]} The Shell created from the template,a nd the fragment cloned from the Shell.*/
101
+ populateFromTemplate(template) {
103
102
  /*#IFDEV*/assert(this.rootNg);/*#ENDIF*/
104
-
105
- /** @type {Template} */
106
103
  this.template = template;
107
-
108
- // new! Is this needed?
109
- template.nodeGroup = this;
110
-
111
- // Get a cached version of the parsed and instantiated html, and ExprPaths.
104
+ this.exactKey = template.getExactKey();
105
+ this.closeKey = template.getCloseKey();
112
106
 
113
107
  // If it's just a text node, skip a bunch of unnecessary steps.
114
- if (!(this instanceof RootNodeGroup) && !template.exprs.length && !template.html[0].includes('<')) {
115
- //let doc = this.rootNg.startNode?.ownerDocument || document;
116
- let textNode = document.createTextNode(template.html[0]);
117
-
108
+ if (template.isText) {
109
+ let textNode = Globals.doc.createTextNode(template.html[0]);
118
110
  this.startNode = this.endNode = textNode;
119
111
  return [];
120
112
  }
113
+
114
+ // Get a cached version of the parsed and instantiated html, and ExprPaths:
121
115
  else {
122
116
  let shell = Shell.get(template.html);
123
117
  let fragment = shell.fragment.cloneNode(true);
124
118
 
125
- if (fragment instanceof DocumentFragment) {
119
+ if (fragment?.nodeType === 11) { // DocumentFragment
126
120
  let childNodes = fragment.childNodes;
127
121
  this.startNode = childNodes[0];
128
122
  this.endNode = childNodes[childNodes.length - 1];
129
123
  }
130
- else {
124
+ else
131
125
  this.startNode = this.endNode = fragment;
132
- }
126
+
133
127
  return [fragment, shell];
134
128
  }
135
129
  }
@@ -168,6 +162,7 @@ export default class NodeGroup {
168
162
  exprIndex--;
169
163
  }
170
164
 
165
+
171
166
  // TODO: Need to end and restart this block when going from one component to the next?
172
167
  // Think of having two adjacent components.
173
168
  // But the dynamicAttribsAdjacet test already passes.
@@ -176,11 +171,11 @@ export default class NodeGroup {
176
171
  // 1. Instantiate it if it hasn't already been, sending all expr's to its constructor.
177
172
  // 2. Otherwise send them to its render function.
178
173
  // Components with no expressions as attributes are instead activated in activateEmbeds().
179
- if (path.nodeMarker !== this.rootNg.root && path.isComponent()) {
174
+ if (path.nodeMarker !== this.rootNg.root && path.isComponent) {
180
175
 
181
- if (!nextPath || !nextPath.isComponent() || nextPath.nodeMarker !== path.nodeMarker)
176
+ if (!nextPath || !nextPath.isComponent || nextPath.nodeMarker !== path.nodeMarker)
182
177
  lastComponentPathIndex = i;
183
- let isFirstComponentPath = !prevPath || !prevPath.isComponent() || prevPath.nodeMarker !== path.nodeMarker;
178
+ let isFirstComponentPath = !prevPath || !prevPath.isComponent || prevPath.nodeMarker !== path.nodeMarker;
184
179
 
185
180
  if (isFirstComponentPath) {
186
181
 
@@ -190,7 +185,7 @@ export default class NodeGroup {
190
185
  componentProps[attrName] = pathExprs[j].length > 1 ? pathExprs[j].join('') : pathExprs[j][0];
191
186
  }
192
187
 
193
- this.applyComponentExprs(path.nodeMarker, componentProps);
188
+ this.handleComponent(path.nodeMarker, componentProps, true);
194
189
 
195
190
  // Set attributes on component.
196
191
  for (let j=i; j<=lastComponentPathIndex; j++)
@@ -209,7 +204,10 @@ export default class NodeGroup {
209
204
  // TODO: Only do this if we have ExprPaths within styles?
210
205
  this.updateStyles();
211
206
 
212
-
207
+ // Call render() on static web components. This makes the component.staticAttribs() test work.
208
+ for (let el of this.staticComponents)
209
+ if (el.render)
210
+ el.render(Util.attribsToObject(el)); // It has no expressions.
213
211
 
214
212
  // Invalidate the nodes cache because we just changed it.
215
213
  this.nodesCache = null;
@@ -225,50 +223,39 @@ export default class NodeGroup {
225
223
  }
226
224
 
227
225
  /**
228
- * Create a nested Component or call render with the new props.
229
- * @param el {Solarite:HTMLElement}
230
- * @param props {Object} */
231
- applyComponentExprs(el, props) {
232
-
233
- // TODO: Does a hash of this already exist somewhere?
234
- // Perhaps if Components were treated as child NodeGroups, which would need to be the child of an ExprPath,
235
- // then we could re-use the hash and logic from NodeManager?
236
- let newHash = getObjectHash(props);
237
-
226
+ * Unified path to ensure a child component is instantiated (if placeholder) and optionally rendered.
227
+ * @param el {HTMLElement}
228
+ * @param props {?Object}
229
+ * @param doRender {boolean}
230
+ * @return {HTMLElement} The (possibly replaced) element. */
231
+ handleComponent(el, props=null, doRender=true) {
238
232
  let isPreHtmlElement = el.hasAttribute('solarite-placeholder');
239
- let isPreIsElement = el.hasAttribute('_is')
240
-
241
-
242
- // Instantiate a placeholder.
233
+ let isPreIsElement = el.hasAttribute('_is');
234
+ let attribs, children;
243
235
  if (isPreHtmlElement || isPreIsElement)
244
- el = this.instantiateComponent(el, isPreHtmlElement, props);
245
-
246
- // Call render() with the same params that would've been passed to the constructor.
247
- // We do this even if the arguments haven't changed, so we can let the child component
248
- // compare the arguments and then decide for itself whether it wants to re-render.
249
- else if (el.render) {
250
- //let oldHash = Globals.componentArgsHash.get(el);
251
- //if (oldHash !== newHash) { // Only if not changed.
252
- let args = {};
236
+ [el, attribs, children] = this.instantiateComponent(el, isPreHtmlElement, props);
237
+ if (doRender && el.render) {
238
+ if (!attribs) {
239
+ attribs = Util.attribsToObject(el);
253
240
  for (let name in props || {})
254
- args[Util.dashesToCamel(name)] = props[name];
255
- el.render(args); // Pass new values of props to render so it can decide how it wants to respond.
256
- //}
241
+ attribs[Util.dashesToCamel(name)] = props[name];
242
+ children = el.childNodes;
243
+ }
244
+ el.render(attribs, children);
257
245
  }
258
-
259
- Globals.componentArgsHash.set(el, newHash);
246
+ return el;
260
247
  }
261
-
248
+
262
249
  /**
263
250
  * We swap the placeholder element for the real element so we can pass its dynamic attributes
264
251
  * to its constructor.
252
+ * This is only called by handleComponent()
253
+ * This does not call render()
265
254
  *
266
- * The logic of this function is complex and could use cleaning up.
267
- *
268
- * @param el
255
+ * @param el {HTMLElement}
269
256
  * @param isPreHtmlElement {?boolean} True if the element's tag name ends with -solarite-placeholder
270
257
  * @param props {Object} Attributes with dynamic values.
271
- * @return {HTMLElement} */
258
+ * @return {[HTMLElement, attribs:Object, children:Node[]]}} */
272
259
  instantiateComponent(el, isPreHtmlElement=undefined, props=undefined) {
273
260
  if (isPreHtmlElement === undefined)
274
261
  isPreHtmlElement = !el.hasAttribute('_is');
@@ -283,24 +270,17 @@ export default class NodeGroup {
283
270
  if (!Constructor)
284
271
  throw new Error(`The custom tag name ${tagName} is not registered.`)
285
272
 
286
- let args = {};
287
- for (let name in props || {})
288
- args[Util.dashesToCamel(name)] = props[name];
289
-
290
273
  // Pass other attribs to constructor, since otherwise they're not yet set on the element,
291
274
  // and the constructor would otherwise have no way to see them.
292
- if (el.attributes.length) {
293
- for (let attrib of el.attributes) {
294
- let attribName = Util.dashesToCamel(attrib.name);
295
- if (!args.hasOwnProperty(attribName) && attribName !== 'solarite-placeholder')
296
- args[attribName] = attrib.value;
297
- }
298
- }
275
+ let attribs = Util.attribsToObject(el, 'solarite-placeholder');
276
+ for (let name in props || {})
277
+ attribs[Util.dashesToCamel(name)] = props[name];
278
+
299
279
 
300
280
  // Create the web component.
301
281
  // Get the children that aren't Solarite's comment placeholders.
302
- let ch = [...el.childNodes].filter(node => node.nodeType !== Node.COMMENT_NODE || !node.nodeValue.startsWith('ExprPath'));
303
- let newEl = new Constructor(args, ch);
282
+ let children = [...el.childNodes].filter(node => node.nodeType !== Node.COMMENT_NODE || !node.nodeValue.startsWith('ExprPath'));
283
+ let newEl = new Constructor(attribs, children);
304
284
 
305
285
  if (!isPreHtmlElement)
306
286
  newEl.setAttribute('is', el.getAttribute('is').toLowerCase());
@@ -326,7 +306,7 @@ export default class NodeGroup {
326
306
  if (this.endNode === el)
327
307
  this.endNode = newEl;
328
308
 
329
-
309
+ // This is used only if inheriting from the Solarite class.
330
310
  // applyComponentExprs() is called because we're rendering.
331
311
  // So we want to render the sub-component also.
332
312
  if (newEl.renderFirstTime)
@@ -350,7 +330,7 @@ export default class NodeGroup {
350
330
  newEl.setAttribute(name, val);
351
331
  }
352
332
 
353
- return newEl;
333
+ return [newEl, attribs, children];
354
334
  }
355
335
 
356
336
  /**
@@ -397,18 +377,21 @@ export default class NodeGroup {
397
377
  * Requires the nodeCache to be present. */
398
378
  removeAndSaveOrphans() {
399
379
  /*#IFDEV*/assert(this.nodesCache);/*#ENDIF*/
400
- let fragment = document.createDocumentFragment();
380
+ let fragment = Globals.doc.createDocumentFragment();
401
381
  for (let node of this.getNodes())
402
382
  fragment.append(node);
403
383
  }
404
384
 
405
385
 
406
- updatePaths(fragment, paths, offset) {
407
- // Update paths to point to the fragment.
386
+ /**
387
+ * @param fragment {DocumentFragment}
388
+ * @param paths
389
+ * @param startingPathDepth {int} */
390
+ updatePaths(fragment, paths, startingPathDepth) {
408
391
  let pathLength = paths.length;
409
392
  this.paths.length = pathLength;
410
393
  for (let i=0; i<pathLength; i++) {
411
- let path = paths[i].clone(fragment, offset)
394
+ let path = paths[i].clone(fragment, startingPathDepth)
412
395
  path.parentNg = this;
413
396
  this.paths[i] = path;
414
397
  }
@@ -435,7 +418,7 @@ export default class NodeGroup {
435
418
  `parentNode: ${this.parentNode?.tagName?.toLowerCase()}`,
436
419
  'nodes:',
437
420
  ...setIndent(this.getNodes().map(item => {
438
- if (item instanceof Node) {
421
+ if (item?.nodeType) {
439
422
 
440
423
  let tree = nodeToArrayTree(item, nextNode => {
441
424
 
@@ -490,7 +473,7 @@ export default class NodeGroup {
490
473
  }
491
474
  //#ENDIF
492
475
 
493
- findStaticComponents(root, shell, pathOffset=0) {
476
+ findStaticComponents(root, shell, startingPathDepth=0) {
494
477
  let result = [];
495
478
 
496
479
  // static components. These are WebComponents that do not have any constructor arguments that are expressions.
@@ -498,8 +481,8 @@ export default class NodeGroup {
498
481
  // Maybe someday these two paths will be merged?
499
482
  // Must happen before ids because instantiateComponent will replace the element.
500
483
  for (let path of shell.staticComponents) {
501
- if (pathOffset)
502
- path = path.slice(0, -pathOffset);
484
+ if (startingPathDepth)
485
+ path = path.slice(0, -startingPathDepth);
503
486
  let el = resolveNodePath(root, path);
504
487
 
505
488
  // Shell doesn't know if a web component is the pseudoRoot so we have to detect it here.
@@ -511,8 +494,9 @@ export default class NodeGroup {
511
494
  }
512
495
 
513
496
  instantiateStaticComponents(staticComponents) {
514
- for (let el of staticComponents)
515
- this.instantiateComponent(el)
497
+ // TODO: Why do we not call render() on the static component here? The tests pass either way.
498
+ for (let i in staticComponents)
499
+ staticComponents[i] = this.handleComponent(staticComponents[i], null, false);
516
500
  }
517
501
 
518
502
  /**
@@ -533,7 +517,7 @@ export default class NodeGroup {
533
517
  let el = resolveNodePath(root, path);
534
518
  Util.bindId(rootEl, el);
535
519
  }
536
- }
520
+ }
537
521
 
538
522
  // styles
539
523
  if (options?.styles !== false) {
@@ -566,150 +550,3 @@ export default class NodeGroup {
566
550
  }
567
551
 
568
552
 
569
- export class RootNodeGroup extends NodeGroup {
570
-
571
- /**
572
- * Root node at the top of the hierarchy.
573
- * @type {HTMLElement} */
574
- root;
575
-
576
- /**
577
- * When we call renerWatched() we re-render these expressions, then clear this to a new Map()
578
- * @type {Map<ExprPath, ValueOp|WholeArrayOp|ArraySpliceOp[]>} */
579
- exprsToRender = new Map();
580
-
581
- /**
582
- *
583
- * @param template
584
- * @param el
585
- * @param options {?object}
586
- */
587
- constructor(template, el, options) {
588
- super(template);
589
-
590
- this.options = options;
591
-
592
- this.rootNg = this;
593
- let [fragment, shell] = this.init(template);
594
-
595
- if (fragment instanceof Text) {
596
-
597
- if (el) {
598
- this.startNode = el;
599
- this.endNode = el;
600
- if (fragment.nodeValue.length)
601
- el.append(fragment);
602
- this.root = el;
603
- }
604
- Globals.nodeGroups.set(this.root, this);
605
- }
606
- else {
607
-
608
- // If adding NodeGroup to an element.
609
- let offset = 0;
610
- let root = fragment; // TODO: Rename so it's not confused with this.root.
611
- if (el) {
612
- Globals.nodeGroups.set(el, this);
613
-
614
- // Save slot children
615
- let slotChildren;
616
- if (el.childNodes.length) {
617
- slotChildren = document.createDocumentFragment();
618
- slotChildren.append(...el.childNodes);
619
- }
620
-
621
- this.root = el;
622
-
623
- // If el should replace the root node of the fragment.
624
- if (isReplaceEl(fragment, el)) {
625
- el.append(...fragment.children[0].childNodes);
626
-
627
- // Copy attributes
628
- for (let attrib of fragment.children[0].attributes)
629
- if (!el.hasAttribute(attrib.name) && attrib.name !== 'solarite-placeholder')
630
- el.setAttribute(attrib.name, attrib.value);
631
-
632
- // Go one level deeper into all of shell's paths.
633
- offset = 1;
634
- } else {
635
- let isEmpty = fragment.childNodes.length === 1 && fragment.childNodes[0].nodeType === 3 && fragment.childNodes[0].textContent === '';
636
- if (!isEmpty)
637
- el.append(...fragment.childNodes);
638
- }
639
-
640
- // Setup children
641
- if (slotChildren) {
642
-
643
- // Named slots
644
- for (let slot of el.querySelectorAll('slot[name]')) {
645
- let name = slot.getAttribute('name')
646
- if (name) {
647
- let slotChildren2 = slotChildren.querySelectorAll(`[slot='${name}']`);
648
- slot.append(...slotChildren2);
649
- }
650
- }
651
-
652
- // Unnamed slots
653
- let unamedSlot = el.querySelector('slot:not([name])')
654
- if (unamedSlot)
655
- unamedSlot.append(slotChildren);
656
-
657
- // No slots
658
- else
659
- el.append(slotChildren);
660
- }
661
-
662
- root = el;
663
-
664
- this.startNode = el;
665
- this.endNode = el;
666
- } else {
667
- let singleEl = getSingleEl(fragment);
668
- this.root = singleEl || fragment; // We return the whole fragment when calling r() with a collection of nodes.
669
-
670
- Globals.nodeGroups.set(this.root, this);
671
- if (singleEl) {
672
- root = singleEl;
673
- offset = 1;
674
- }
675
- }
676
-
677
- this.updatePaths(root, shell.paths, offset);
678
-
679
- // Static web components can sometimes have children created via expressions.
680
- // But calling applyExprs() will mess up the shell's path to them.
681
- // So we find them first, then call activateStaticComponents() after their children have been created.
682
- let staticComponents = this.findStaticComponents(root, shell, offset);
683
-
684
- this.activateEmbeds(root, shell, offset);
685
-
686
- // Apply exprs
687
- this.applyExprs(template.exprs);
688
-
689
- this.instantiateStaticComponents(staticComponents);
690
- }
691
- }
692
- }
693
-
694
- function getSingleEl(fragment) {
695
- let nonempty = [];
696
- for (let n of fragment.childNodes) {
697
- if (n.nodeType === 1 || n.nodeType === 3 && n.textContent.trim().length) {
698
- if (nonempty.length)
699
- return null;
700
- nonempty.push(n);
701
- }
702
- }
703
- return nonempty[0];
704
- }
705
-
706
- /**
707
- * Does the fragment have one child that's an element matching the tagname of el?
708
- * @param fragment {DocumentFragment}
709
- * @param el {HTMLElement}
710
- * @returns {boolean} */
711
- function isReplaceEl(fragment, el) {
712
- return fragment.children.length===1
713
- && el.tagName.includes('-') // TODO: Check for solarite-placeholder attribute instead?
714
- && fragment.children[0].tagName.replace('-SOLARITE-PLACEHOLDER', '') === el.tagName;
715
- }
@@ -0,0 +1,153 @@
1
+ import Globals from './Globals.js';
2
+ import NodeGroup from './NodeGroup.js';
3
+
4
+
5
+ export default class RootNodeGroup extends NodeGroup {
6
+
7
+ /**
8
+ * Root node at the top of the hierarchy.
9
+ * @type {HTMLElement} */
10
+ root;
11
+
12
+ /**
13
+ * When we call renerWatched() we re-render these expressions, then clear this to a new Map()
14
+ * @type {Map<ExprPath, ValueOp|WholeArrayOp|ArraySpliceOp[]>} */
15
+ exprsToRender = new Map();
16
+
17
+ /**
18
+ * @param template {Template}
19
+ * @param el {?HTMLElement} Optional, pre-existing htmlElement tat will be the root.
20
+ * @param options {?object} */
21
+ constructor(template, el, options) {
22
+ super(template);
23
+
24
+ this.options = options;
25
+
26
+ let [fragment, shell] = this.populateFromTemplate(template);
27
+
28
+ let startingPathDepth = 0;
29
+
30
+
31
+ if (fragment instanceof Text) {
32
+
33
+ if (el) {
34
+ this.startNode = el;
35
+ this.endNode = el;
36
+ if (fragment.nodeValue.length)
37
+ el.append(fragment);
38
+ this.root = el;
39
+ }
40
+ else
41
+ throw new Error('Cannot create a standalone text node');
42
+ Globals.nodeGroups.set(this.root, this);
43
+ }
44
+
45
+
46
+ else {
47
+
48
+ // If adding NodeGroup to an element.
49
+ if (el) {
50
+ this.root = el;
51
+
52
+ // Save slot children
53
+ let slotChildren;
54
+ if (el.childNodes.length) {
55
+ slotChildren = Globals.doc.createDocumentFragment();
56
+ slotChildren.append(...el.childNodes);
57
+ }
58
+
59
+ // If el should replace the root node of the fragment.
60
+ if (isReplaceEl(fragment, el)) {
61
+ el.append(...fragment.children[0].childNodes);
62
+
63
+ // Copy attributes
64
+ for (let attrib of fragment.children[0].attributes)
65
+ if (!el.hasAttribute(attrib.name) && attrib.name !== 'solarite-placeholder')
66
+ el.setAttribute(attrib.name, attrib.value);
67
+
68
+ // Go one level deeper into all of shell's paths.
69
+ startingPathDepth = 1;
70
+ }
71
+
72
+ else {
73
+ let isEmpty = fragment.childNodes.length === 1 && fragment.childNodes[0].nodeType === 3 && fragment.childNodes[0].textContent === '';
74
+ if (!isEmpty)
75
+ el.append(...fragment.childNodes);
76
+ }
77
+
78
+ // Setup children
79
+ if (slotChildren) {
80
+
81
+ // Named slots
82
+ for (let slot of el.querySelectorAll('slot[name]')) {
83
+ let name = slot.getAttribute('name')
84
+ if (name) {
85
+ let slotChildren2 = slotChildren.querySelectorAll(`[slot='${name}']`);
86
+ slot.append(...slotChildren2);
87
+ }
88
+ }
89
+
90
+ // Unnamed slots
91
+ let unamedSlot = el.querySelector('slot:not([name])')
92
+ if (unamedSlot)
93
+ unamedSlot.append(slotChildren);
94
+
95
+ // No slots
96
+ else
97
+ el.append(slotChildren);
98
+ }
99
+
100
+ this.startNode = el;
101
+ this.endNode = el;
102
+ }
103
+
104
+ // Instantiate as a standalone element.
105
+ else {
106
+ let singleEl = getSingleEl(fragment);
107
+ this.root = singleEl || fragment; // We return the whole fragment when calling h() with a collection of nodes.
108
+
109
+ if (singleEl)
110
+ startingPathDepth = 1;
111
+ }
112
+ Globals.nodeGroups.set(this.root, this);
113
+ this.updatePaths(this.root, shell.paths, startingPathDepth);
114
+
115
+ // Static web components can sometimes have children created via expressions.
116
+ // But calling applyExprs() will mess up the shell's path to them.
117
+ // So we find them first, then call activateStaticComponents() after their children have been created.
118
+ this.staticComponents = this.findStaticComponents(this.root, shell, startingPathDepth);
119
+
120
+ this.activateEmbeds(this.root, shell, startingPathDepth);
121
+
122
+ // Apply exprs
123
+ this.applyExprs(template.exprs);
124
+
125
+ this.instantiateStaticComponents(this.staticComponents);
126
+ }
127
+
128
+
129
+ }
130
+ }
131
+
132
+ function getSingleEl(fragment) {
133
+ let nonempty = [];
134
+ for (let n of fragment.childNodes) {
135
+ if (n.nodeType === 1 || n.nodeType === 3 && n.textContent.trim().length) {
136
+ if (nonempty.length)
137
+ return null;
138
+ nonempty.push(n);
139
+ }
140
+ }
141
+ return nonempty[0];
142
+ }
143
+
144
+ /**
145
+ * Does the fragment have one child that's an element matching the tagname of el?
146
+ * @param fragment {DocumentFragment}
147
+ * @param el {HTMLElement}
148
+ * @returns {boolean} */
149
+ function isReplaceEl(fragment, el) {
150
+ return fragment.children.length===1
151
+ && el.tagName.includes('-') // TODO: Check for solarite-placeholder attribute instead?
152
+ && fragment.children[0].tagName.replace('-SOLARITE-PLACEHOLDER', '') === el.tagName;
153
+ }