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/dist/Solarite-debug.js +597 -420
- package/dist/Solarite.js +593 -373
- package/dist/Solarite.min.js +11 -2
- package/package.json +2 -2
- package/readme.md +2 -2
- package/src/ExprPath.js +110 -91
- package/src/Globals.js +9 -5
- package/src/NodeGroup.js +82 -245
- package/src/RootNodeGroup.js +153 -0
- package/src/Shell.js +12 -9
- package/src/Solarite.d.ts +8 -4
- package/src/Solarite.js +10 -8
- package/src/Template.js +135 -3
- package/src/Util.js +52 -14
- package/src/createSolarite.js +2 -2
- package/src/h.js +81 -129
- package/src/toEl.js +84 -0
- package/src/udomdiff.js +0 -55
- package/src/watch.js +2 -2
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
|
|
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.
|
|
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
|
|
74
|
-
|
|
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
|
-
* @
|
|
93
|
-
|
|
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
|
-
|
|
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 (
|
|
115
|
-
|
|
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
|
|
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
|
|
176
|
+
if (!nextPath || !nextPath.isComponent || nextPath.nodeMarker !== path.nodeMarker)
|
|
182
177
|
lastComponentPathIndex = i;
|
|
183
|
-
let isFirstComponentPath = !prevPath || !prevPath.isComponent
|
|
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.
|
|
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
|
-
*
|
|
229
|
-
* @param el {
|
|
230
|
-
* @param props {Object}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
|
|
255
|
-
el.
|
|
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
|
-
*
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
303
|
-
let newEl = new Constructor(
|
|
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 =
|
|
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
|
-
|
|
407
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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 (
|
|
502
|
-
path = path.slice(0, -
|
|
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
|
-
|
|
515
|
-
|
|
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
|
+
}
|