solarite 0.4.0 → 0.5.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 +1655 -1762
- package/dist/Solarite.js +1679 -1814
- package/dist/Solarite.min.js +2 -11
- package/package.json +11 -1
- package/src/Globals.js +14 -24
- package/src/HtmlParser.js +1 -1
- package/src/MultiValueMap.js +3 -3
- package/src/NodeGroup.js +248 -330
- package/src/Path.js +212 -0
- package/src/PathToAttribValue.js +259 -0
- package/src/PathToAttribs.js +77 -0
- package/src/PathToComment.js +8 -0
- package/src/PathToComponent.js +169 -0
- package/src/PathToEvent.js +57 -0
- package/src/PathToNodes.js +566 -0
- package/src/RootNodeGroup.js +2 -147
- package/src/Shell.js +83 -77
- package/src/Solarite.d.ts +71 -31
- package/src/Solarite.js +124 -14
- package/src/Template.js +39 -40
- package/src/Util.js +22 -38
- package/src/assert.js +5 -5
- package/src/getArg.js +26 -24
- package/src/h.js +23 -20
- package/src/hash.js +11 -9
- package/src/toEl.js +7 -8
- package/src/udomdiff.js +0 -2
- package/src/watch.js +76 -79
- package/src/ExprPath.js +0 -1117
- package/src/createSolarite.js +0 -154
package/src/NodeGroup.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import assert from "./assert.js";
|
|
2
2
|
import Util, {flattenAndIndent, nodeToArrayTree, setIndent} from "./Util.js";
|
|
3
|
-
import delve from "./delve.js";
|
|
4
3
|
import Shell from "./Shell.js";
|
|
5
4
|
import RootNodeGroup from './RootNodeGroup.js';
|
|
6
|
-
import
|
|
5
|
+
import Path from "./Path.js";
|
|
7
6
|
import Globals from './Globals.js';
|
|
7
|
+
import PathToComponent from "./PathToComponent.js";
|
|
8
|
+
import PathToNodes from "./PathToNodes.js";
|
|
8
9
|
|
|
9
10
|
/** @typedef {boolean|string|number|function|Object|Array|Date|Node|Template} Expr */
|
|
10
11
|
|
|
@@ -13,17 +14,14 @@ import Globals from './Globals.js';
|
|
|
13
14
|
*
|
|
14
15
|
* The range is determined by startNode and nodeMarker.
|
|
15
16
|
* startNode - never null. An empty text node is created before the first path if none exists.
|
|
16
|
-
* nodeMarker - null if this Nodegroup is at the end of its parents' nodes
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* */
|
|
17
|
+
* nodeMarker - null if this Nodegroup is at the end of its parents' nodes.*/
|
|
20
18
|
export default class NodeGroup {
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
21
|
* @Type {RootNodeGroup} */
|
|
24
22
|
rootNg;
|
|
25
23
|
|
|
26
|
-
/** @type {
|
|
24
|
+
/** @type {Path} */
|
|
27
25
|
parentPath;
|
|
28
26
|
|
|
29
27
|
/** @type {Node|HTMLElement} First node of NodeGroup. Should never be null. */
|
|
@@ -31,10 +29,10 @@ export default class NodeGroup {
|
|
|
31
29
|
|
|
32
30
|
/** @type {Node|HTMLElement} A node that never changes that this NodeGroup should always insert its nodes before.
|
|
33
31
|
* An empty text node will be created to insertBefore if there's no other NodeMarker and this isn't at the last position.
|
|
34
|
-
* TODO: But sometimes startNode and endNode point to the same node. Document
|
|
32
|
+
* TODO: But sometimes startNode and endNode point to the same node. Document this inconsistency. */
|
|
35
33
|
endNode;
|
|
36
34
|
|
|
37
|
-
/** @type {
|
|
35
|
+
/** @type {Path[]} */
|
|
38
36
|
paths = [];
|
|
39
37
|
|
|
40
38
|
/** @type {string} Key that matches the template and the expressions. */
|
|
@@ -54,283 +52,220 @@ export default class NodeGroup {
|
|
|
54
52
|
* @type {?Map<HTMLStyleElement, string>} */
|
|
55
53
|
styles;
|
|
56
54
|
|
|
57
|
-
dynamicComponents = new Set();
|
|
58
|
-
staticComponents = [];
|
|
59
|
-
|
|
60
55
|
/** @type {Template} */
|
|
61
56
|
template;
|
|
62
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Root node at the top of the hierarchy.
|
|
60
|
+
* Should be moved to RootNodeGroup
|
|
61
|
+
* @type {HTMLElement} */
|
|
62
|
+
root;
|
|
63
|
+
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* Create an "instantiated" NodeGroup from a Template and add it to an element.
|
|
67
|
+
* Don't call applyExprs() yet to apply expressions or instantiate components yet.
|
|
66
68
|
* @param template {Template} Create it from the html strings and expressions in this template.
|
|
67
|
-
* @param parentPath {?
|
|
68
|
-
|
|
69
|
+
* @param parentPath {?Path}
|
|
70
|
+
* @param el {?HTMLElement} Optional, pre-existing htmlElement that will be the root.
|
|
71
|
+
* @param options {?object} Only used for RootNodeGroup */
|
|
72
|
+
constructor(template, parentPath=null, el=null, options=null) {
|
|
69
73
|
this.rootNg = parentPath?.parentNg?.rootNg || this;
|
|
70
74
|
this.parentPath = parentPath;
|
|
71
75
|
|
|
72
|
-
if (!(this instanceof RootNodeGroup)) {
|
|
73
|
-
|
|
74
|
-
let [fragment, shell] = this.populateFromTemplate(template);
|
|
75
|
-
|
|
76
|
-
if (fragment && template.exprs.length) {
|
|
77
|
-
this.updatePaths(fragment, shell.paths);
|
|
78
|
-
|
|
79
|
-
// Static web components can sometimes have children created via expressions.
|
|
80
|
-
// But calling applyExprs() will mess up the shell's path to them.
|
|
81
|
-
// So we find them first, then call instantiateStaticComponents() after their children have been created.
|
|
82
|
-
this.staticComponents = this.findStaticComponents(fragment, shell);
|
|
83
|
-
|
|
84
|
-
this.activateEmbeds(fragment, shell);
|
|
85
|
-
|
|
86
|
-
// Apply exprs
|
|
87
|
-
this.applyExprs(template.exprs);
|
|
88
|
-
|
|
89
|
-
this.instantiateStaticComponents(this.staticComponents);
|
|
90
|
-
}
|
|
91
|
-
else if (shell)
|
|
92
|
-
this.activateEmbeds(fragment, shell);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Common init shared by RootNodeGroup and NodeGroup constructors.
|
|
98
|
-
* But in a separate function because they need to do this at a different step.
|
|
99
|
-
* @param template {Template} Create it from the html strings and expressions in this template.
|
|
100
|
-
* @returns {[DocumentFragment, Shell]} The Shell created from the template,a nd the fragment cloned from the Shell.*/
|
|
101
|
-
populateFromTemplate(template) {
|
|
102
76
|
/*#IFDEV*/assert(this.rootNg);/*#ENDIF*/
|
|
103
77
|
this.template = template;
|
|
104
|
-
this.exactKey = template.getExactKey();
|
|
105
78
|
this.closeKey = template.getCloseKey();
|
|
106
79
|
|
|
107
80
|
// If it's just a text node, skip a bunch of unnecessary steps.
|
|
108
81
|
if (template.isText) {
|
|
109
|
-
|
|
110
|
-
this.startNode = this.endNode = textNode;
|
|
111
|
-
return [];
|
|
82
|
+
this.startNode = this.endNode = Globals.doc.createTextNode(template.html[0]);
|
|
112
83
|
}
|
|
113
84
|
|
|
114
|
-
// Get a cached version of the parsed and instantiated html, and ExprPaths:
|
|
115
85
|
else {
|
|
116
|
-
|
|
117
|
-
|
|
86
|
+
// Get a cached version of the parsed and instantiated html, and Paths:
|
|
87
|
+
const shell = Shell.get(template.html);
|
|
88
|
+
const shellFragment = shell.fragment.cloneNode(true);
|
|
118
89
|
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
this.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
else
|
|
125
|
-
this.startNode = this.endNode = fragment;
|
|
90
|
+
if (shellFragment.nodeType === 11) { // DocumentFragment
|
|
91
|
+
this.startNode = shellFragment.firstChild;
|
|
92
|
+
this.endNode = shellFragment.lastChild;
|
|
93
|
+
} else
|
|
94
|
+
this.startNode = this.endNode = shellFragment;
|
|
126
95
|
|
|
127
|
-
return [fragment, shell];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
96
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
* Dispatches expression handling to other functions depending on the path type.
|
|
134
|
-
* @param exprs {(*|*[]|function|Template)[]}
|
|
135
|
-
* @param paths {?ExprPath[]} Optional. Only used for testing. Normally uses this.paths. */
|
|
136
|
-
applyExprs(exprs, paths=null) {
|
|
137
|
-
paths = paths || this.paths;
|
|
138
|
-
|
|
139
|
-
/*#IFDEV*/
|
|
140
|
-
this.verify();/*#ENDIF*/
|
|
97
|
+
// Special setup for RootNodeGroup
|
|
98
|
+
if (this instanceof RootNodeGroup) {
|
|
141
99
|
|
|
142
|
-
// Things to consider:
|
|
143
|
-
// 1. One path may use multipe expressions. E.g. <div class="${1} ${2}">
|
|
144
|
-
// 2. One component may need to use multiple attribute paths to be instantiated.
|
|
145
|
-
// 3. We apply them in reverse order so that a <select> box has its children created from an expression
|
|
146
|
-
// before its instantiated and its value attribute is set via an expression.
|
|
147
|
-
|
|
148
|
-
let exprIndex = exprs.length - 1; // Update exprs at paths.
|
|
149
|
-
let lastComponentPathIndex;
|
|
150
|
-
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.
|
|
151
|
-
for (let i = paths.length - 1, path; path = paths[i]; i--) {
|
|
152
|
-
let prevPath = paths[i - 1];
|
|
153
|
-
let nextPath = paths[i + 1];
|
|
154
|
-
|
|
155
|
-
// Get the expressions associated with this path.
|
|
156
|
-
if (path.attrValue?.length > 2) {
|
|
157
|
-
let startIndex = (exprIndex - (path.attrValue.length - 1)) + 1;
|
|
158
|
-
pathExprs[i] = exprs.slice(startIndex, exprIndex + 1); // probably doesn't allocate if the JS vm implements copy on write.
|
|
159
|
-
exprIndex -= pathExprs[i].length;
|
|
160
|
-
} else {
|
|
161
|
-
pathExprs[i] = [exprs[exprIndex]];
|
|
162
|
-
exprIndex--;
|
|
163
|
-
}
|
|
164
100
|
|
|
101
|
+
let startingPathDepth = 0;
|
|
102
|
+
this.options = options;
|
|
103
|
+
if (shellFragment instanceof Text) {
|
|
104
|
+
if (!el)
|
|
105
|
+
throw new Error('Cannot create a standalone text node');
|
|
165
106
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
107
|
+
this.root = el;
|
|
108
|
+
if (shellFragment.nodeValue.length)
|
|
109
|
+
this.root.append(shellFragment);
|
|
110
|
+
}
|
|
169
111
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
112
|
+
else {
|
|
113
|
+
if (el) {
|
|
114
|
+
this.root = el;
|
|
115
|
+
|
|
116
|
+
// Save slot
|
|
117
|
+
// 1. Globals.currentSlotChildren is set if this is called via PathToComponent.applyComponent() calls render()
|
|
118
|
+
// 2. el.childNodes is set if render() is called manually for the first time.
|
|
119
|
+
let slotChildren;
|
|
120
|
+
if (Globals.currentSlotChildren || el.childNodes.length) {
|
|
121
|
+
slotChildren = Globals.doc.createDocumentFragment();
|
|
122
|
+
slotChildren.append(...(Globals.currentSlotChildren || el.childNodes));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// If el should replace the root node of the fragment.
|
|
126
|
+
if (isReplaceEl(shellFragment, this.root.tagName)) {
|
|
127
|
+
this.root.append(...shellFragment.children[0].childNodes);
|
|
128
|
+
|
|
129
|
+
// Copy attributes
|
|
130
|
+
for (let attrib of shellFragment.children[0].attributes)
|
|
131
|
+
if (!this.root.hasAttribute(attrib.name))
|
|
132
|
+
this.root.setAttribute(attrib.name, attrib.value);
|
|
133
|
+
|
|
134
|
+
// Go one level deeper into all of shell's paths.
|
|
135
|
+
startingPathDepth = 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
else {
|
|
139
|
+
let isEmpty = shellFragment.childNodes.length === 1 && shellFragment.childNodes[0].nodeType === 3 && shellFragment.childNodes[0].textContent === '';
|
|
140
|
+
if (!isEmpty)
|
|
141
|
+
this.root.append(...shellFragment.childNodes);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
// Setup slot children (deprecated)
|
|
146
|
+
if (slotChildren) {
|
|
147
|
+
// Named slots
|
|
148
|
+
for (let slot of el.querySelectorAll('slot[name]')) {
|
|
149
|
+
let name = slot.getAttribute('name')
|
|
150
|
+
if (name) {
|
|
151
|
+
let slotChildren2 = slotChildren.querySelectorAll(`[slot='${name}']`);
|
|
152
|
+
slot.append(...slotChildren2);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Unnamed slots
|
|
156
|
+
let unamedSlot = el.querySelector('slot:not([name])')
|
|
157
|
+
if (unamedSlot)
|
|
158
|
+
unamedSlot.append(slotChildren);
|
|
159
|
+
// No slots
|
|
160
|
+
else
|
|
161
|
+
el.append(slotChildren);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
175
164
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
165
|
+
// Instantiate as a standalone element.
|
|
166
|
+
else {
|
|
167
|
+
let onlyChild = getSingleEl(shellFragment);
|
|
168
|
+
this.root = onlyChild || shellFragment; // We return the whole fragment when calling h() with a collection of nodes.
|
|
169
|
+
if (onlyChild)
|
|
170
|
+
startingPathDepth = 1;
|
|
171
|
+
}
|
|
179
172
|
|
|
180
|
-
|
|
173
|
+
// Exclude the path to ourself. Otherwise we get infinite recursion.
|
|
174
|
+
// let paths = [...shell.paths];
|
|
175
|
+
// if (paths[0] instanceof PathToComponent)
|
|
176
|
+
// paths.shift();
|
|
181
177
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
178
|
+
this.setPathsFromFragment(this.root, shell.paths, startingPathDepth);
|
|
179
|
+
this.activateEmbeds(this.root, shell, startingPathDepth);
|
|
180
|
+
}
|
|
181
|
+
this.startNode = this.endNode = this.root;
|
|
187
182
|
|
|
188
|
-
|
|
183
|
+
Globals.rootNodeGroups.set(this.root, this);
|
|
184
|
+
} // end if RootNodeGroup
|
|
189
185
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
186
|
+
else if (shell) {
|
|
187
|
+
if (shell.paths.length) {
|
|
188
|
+
this.setPathsFromFragment(shellFragment, shell.paths);
|
|
193
189
|
}
|
|
190
|
+
|
|
191
|
+
this.activateEmbeds(shellFragment, shell);
|
|
194
192
|
}
|
|
193
|
+
}
|
|
195
194
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
//#IFDEV
|
|
196
|
+
this.verify();
|
|
197
|
+
//#ENDIF
|
|
198
|
+
}
|
|
199
199
|
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Use the paths to insert the given expressions.
|
|
203
|
+
* Dispatches expression handling to other functions depending on the path type.
|
|
204
|
+
* @param exprs {(*|*[]|function|Template)[]}
|
|
205
|
+
* @param changed {boolean} If true, the expr's have changed since the last time thsi function was called.
|
|
206
|
+
* @param includeNonComponents {boolean}
|
|
207
|
+
* We still need to call PathToComponent.apply() even if changed=false so the user can handle the rendering. */
|
|
208
|
+
applyExprs(exprs, changed=true, includeNonComponents=true) {
|
|
202
209
|
|
|
210
|
+
/*#IFDEV*/
|
|
211
|
+
this.verify();
|
|
212
|
+
/*#ENDIF*/
|
|
203
213
|
|
|
204
|
-
|
|
205
|
-
this.updateStyles();
|
|
214
|
+
let paths = this.paths;
|
|
206
215
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
// Things to consider:
|
|
217
|
+
// 1. Paths consume a varying number of expressions.
|
|
218
|
+
// An PathToAttribs may use multipe expressions. E.g. <div class="${1} ${2}">
|
|
219
|
+
// While an PathToComponent uses zero.
|
|
220
|
+
// 2. An PathToComponent references other Paths that set its attribute values.
|
|
221
|
+
// 3. We apply them in reverse order so that a <select> box has its children created from an expression
|
|
222
|
+
// before its instantiated and its value attribute is set via an expression.
|
|
223
|
+
let exprIndex = exprs.length; // Update exprs at paths.
|
|
224
|
+
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.
|
|
225
|
+
for (let i = paths.length - 1, path; path = paths[i]; i--) {
|
|
226
|
+
if (i===0 && path instanceof PathToComponent && path.nodeMarker === this.getRootNode())
|
|
227
|
+
continue;
|
|
211
228
|
|
|
212
|
-
|
|
213
|
-
|
|
229
|
+
// Get the expressions associated with this path.
|
|
230
|
+
let exprCount = path.getExpressionCount();
|
|
231
|
+
pathExprs[i] = exprs.slice(exprIndex-exprCount, exprIndex); // slice() probably doesn't allocate if the JS vm implements copy on write.
|
|
232
|
+
exprIndex -= exprCount;
|
|
233
|
+
|
|
234
|
+
// Component expressions don't have a corresponding user-provided expression.
|
|
235
|
+
// They use expressions from the paths that provide their attributes.
|
|
236
|
+
if (path instanceof PathToComponent) {
|
|
237
|
+
let attribExprs = pathExprs.slice(i+1, i+1 + path.attribPaths.length); // +1 b/c we move forward from the component path.
|
|
238
|
+
path.apply(attribExprs, true, changed);
|
|
239
|
+
}
|
|
240
|
+
else if (includeNonComponents)
|
|
241
|
+
path.apply(pathExprs[i]);
|
|
242
|
+
}
|
|
214
243
|
|
|
215
244
|
// If there's leftover expressions, there's probably an issue with the Shell that created this NodeGroup,
|
|
216
245
|
// and the number of paths not matching.
|
|
217
246
|
/*#IFDEV*/
|
|
218
|
-
assert(exprIndex ===
|
|
247
|
+
assert(exprIndex === 0);
|
|
248
|
+
/*#ENDIF*/
|
|
219
249
|
|
|
220
250
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
251
|
+
if (includeNonComponents) {
|
|
252
|
+
|
|
253
|
+
// TODO: Only do this if we have Paths within styles?
|
|
254
|
+
this.updateStyles();
|
|
255
|
+
|
|
256
|
+
// Invalidate the nodes cache because we just changed it.
|
|
257
|
+
this.nodesCache = null;
|
|
224
258
|
|
|
225
|
-
/**
|
|
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) {
|
|
232
|
-
let isPreHtmlElement = el.hasAttribute('solarite-placeholder');
|
|
233
|
-
let isPreIsElement = el.hasAttribute('_is');
|
|
234
|
-
let attribs, children;
|
|
235
|
-
if (isPreHtmlElement || isPreIsElement)
|
|
236
|
-
[el, attribs, children] = this.instantiateComponent(el, isPreHtmlElement, props);
|
|
237
|
-
if (doRender && el.render) {
|
|
238
|
-
if (!attribs) {
|
|
239
|
-
attribs = Util.attribsToObject(el);
|
|
240
|
-
for (let name in props || {})
|
|
241
|
-
attribs[Util.dashesToCamel(name)] = props[name];
|
|
242
|
-
children = el.childNodes;
|
|
243
|
-
}
|
|
244
|
-
el.render(attribs, children);
|
|
245
259
|
}
|
|
246
|
-
|
|
260
|
+
|
|
261
|
+
/*#IFDEV*/
|
|
262
|
+
this.verify();
|
|
263
|
+
/*#ENDIF*/
|
|
247
264
|
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* We swap the placeholder element for the real element so we can pass its dynamic attributes
|
|
251
|
-
* to its constructor.
|
|
252
|
-
* This is only called by handleComponent()
|
|
253
|
-
* This does not call render()
|
|
254
|
-
*
|
|
255
|
-
* @param el {HTMLElement}
|
|
256
|
-
* @param isPreHtmlElement {?boolean} True if the element's tag name ends with -solarite-placeholder
|
|
257
|
-
* @param props {Object} Attributes with dynamic values.
|
|
258
|
-
* @return {[HTMLElement, attribs:Object, children:Node[]]}} */
|
|
259
|
-
instantiateComponent(el, isPreHtmlElement=undefined, props=undefined) {
|
|
260
|
-
if (isPreHtmlElement === undefined)
|
|
261
|
-
isPreHtmlElement = !el.hasAttribute('_is');
|
|
262
|
-
|
|
263
|
-
let tagName = (isPreHtmlElement
|
|
264
|
-
? el.tagName.slice(0, -21) // Remove -SOLARITE-PLACEHOLDER
|
|
265
|
-
: el.getAttribute('is')).toLowerCase();
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// Throw if custom element isn't defined.
|
|
269
|
-
let Constructor = customElements.get(tagName);
|
|
270
|
-
if (!Constructor)
|
|
271
|
-
throw new Error(`The custom tag name ${tagName} is not registered.`)
|
|
272
|
-
|
|
273
|
-
// Pass other attribs to constructor, since otherwise they're not yet set on the element,
|
|
274
|
-
// and the constructor would otherwise have no way to see them.
|
|
275
|
-
let attribs = Util.attribsToObject(el, 'solarite-placeholder');
|
|
276
|
-
for (let name in props || {})
|
|
277
|
-
attribs[Util.dashesToCamel(name)] = props[name];
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// Create the web component.
|
|
281
|
-
// Get the children that aren't Solarite's comment placeholders.
|
|
282
|
-
let children = [...el.childNodes].filter(node => node.nodeType !== Node.COMMENT_NODE || !node.nodeValue.startsWith('ExprPath'));
|
|
283
|
-
let newEl = new Constructor(attribs, children);
|
|
284
|
-
|
|
285
|
-
if (!isPreHtmlElement)
|
|
286
|
-
newEl.setAttribute('is', el.getAttribute('is').toLowerCase());
|
|
287
|
-
|
|
288
|
-
// Replace the placeholder tag with the instantiated web component.
|
|
289
|
-
el.replaceWith(newEl);
|
|
290
|
-
|
|
291
|
-
// If an id pointed at the placeholder, update it to point to the new element.
|
|
292
|
-
let id = el.getAttribute('data-id') || el.getAttribute('id');
|
|
293
|
-
if (id)
|
|
294
|
-
delve(this.getRootNode(), id.split(/\./g), newEl);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
// Update paths to use replaced element.
|
|
298
|
-
for (let path of this.paths) {
|
|
299
|
-
if (path.nodeMarker === el)
|
|
300
|
-
path.nodeMarker = newEl;
|
|
301
|
-
if (path.nodeBefore === el)
|
|
302
|
-
path.nodeBefore = newEl;
|
|
303
|
-
}
|
|
304
|
-
if (this.startNode === el)
|
|
305
|
-
this.startNode = newEl;
|
|
306
|
-
if (this.endNode === el)
|
|
307
|
-
this.endNode = newEl;
|
|
308
|
-
|
|
309
|
-
// This is used only if inheriting from the Solarite class.
|
|
310
|
-
// applyComponentExprs() is called because we're rendering.
|
|
311
|
-
// So we want to render the sub-component also.
|
|
312
|
-
if (newEl.renderFirstTime)
|
|
313
|
-
newEl.renderFirstTime();
|
|
314
|
-
|
|
315
|
-
// Copy attributes over.
|
|
316
|
-
for (let attrib of el.attributes)
|
|
317
|
-
if (attrib.name !== '_is' && attrib.name !== 'solarite-placeholder')
|
|
318
|
-
newEl.setAttribute(attrib.name, attrib.value);
|
|
319
|
-
|
|
320
|
-
// Set dynamic attributes if they are primitive types.
|
|
321
|
-
for (let name in props) {
|
|
322
|
-
let val = props[name];
|
|
323
|
-
if (typeof val === 'boolean') {
|
|
324
|
-
if (val !== false && val !== undefined && val !== null)
|
|
325
|
-
newEl.setAttribute(name, '');
|
|
326
|
-
}
|
|
327
265
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
newEl.setAttribute(name, val);
|
|
331
|
-
}
|
|
266
|
+
// TODO: Give it a better name.
|
|
267
|
+
applyExprs2(exprs) {
|
|
332
268
|
|
|
333
|
-
return [newEl, attribs, children];
|
|
334
269
|
}
|
|
335
270
|
|
|
336
271
|
/**
|
|
@@ -356,10 +291,6 @@ export default class NodeGroup {
|
|
|
356
291
|
return result;
|
|
357
292
|
}
|
|
358
293
|
|
|
359
|
-
getParentNode() {
|
|
360
|
-
return this.startNode?.parentNode
|
|
361
|
-
}
|
|
362
|
-
|
|
363
294
|
/**
|
|
364
295
|
* Get the root element of the NodeGroup's RootNodeGroup.
|
|
365
296
|
* @returns {HTMLElement|DocumentFragment} */
|
|
@@ -374,21 +305,12 @@ export default class NodeGroup {
|
|
|
374
305
|
}
|
|
375
306
|
|
|
376
307
|
/**
|
|
377
|
-
*
|
|
378
|
-
|
|
379
|
-
/*#IFDEV*/assert(this.nodesCache);/*#ENDIF*/
|
|
380
|
-
let fragment = Globals.doc.createDocumentFragment();
|
|
381
|
-
for (let node of this.getNodes())
|
|
382
|
-
fragment.append(node);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* @param fragment {DocumentFragment}
|
|
308
|
+
* Copy paths in fragment to this.paths.
|
|
309
|
+
* @param fragment {DocumentFragment|HTMLElement}
|
|
388
310
|
* @param paths
|
|
389
311
|
* @param startingPathDepth {int} */
|
|
390
|
-
|
|
391
|
-
let pathLength = paths.length;
|
|
312
|
+
setPathsFromFragment(fragment, paths, startingPathDepth=0) {
|
|
313
|
+
let pathLength = paths.length; // For faster iteration
|
|
392
314
|
this.paths.length = pathLength;
|
|
393
315
|
for (let i=0; i<pathLength; i++) {
|
|
394
316
|
let path = paths[i].clone(fragment, startingPathDepth)
|
|
@@ -406,12 +328,59 @@ export default class NodeGroup {
|
|
|
406
328
|
}
|
|
407
329
|
}
|
|
408
330
|
|
|
409
|
-
//#IFDEV
|
|
410
331
|
/**
|
|
411
|
-
* @
|
|
412
|
-
*
|
|
413
|
-
* @
|
|
414
|
-
|
|
332
|
+
* @param root {HTMLElement|DocumentFragment}
|
|
333
|
+
* @param shell {Shell}
|
|
334
|
+
* @param pathOffset {int} */
|
|
335
|
+
activateEmbeds(root, shell, pathOffset=0) {
|
|
336
|
+
|
|
337
|
+
let rootEl = this.rootNg.root;
|
|
338
|
+
if (rootEl) {
|
|
339
|
+
let options = this.rootNg.options;
|
|
340
|
+
|
|
341
|
+
// ids
|
|
342
|
+
if (options?.ids !== false) {
|
|
343
|
+
for (let path of shell.ids) {
|
|
344
|
+
if (pathOffset)
|
|
345
|
+
path = path.slice(0, -pathOffset);
|
|
346
|
+
let el = Path.resolve(root, path);
|
|
347
|
+
Util.bindId(rootEl, el);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// styles
|
|
352
|
+
if (options?.styles !== false) {
|
|
353
|
+
if (shell.styles.length)
|
|
354
|
+
this.styles = new Map();
|
|
355
|
+
for (let path of shell.styles) {
|
|
356
|
+
if (pathOffset)
|
|
357
|
+
path = path.slice(0, -pathOffset);
|
|
358
|
+
|
|
359
|
+
/** @type {HTMLStyleElement} */
|
|
360
|
+
let style = Path.resolve(root, path);
|
|
361
|
+
if (rootEl.nodeType === 1) {
|
|
362
|
+
Util.bindStyles(style, rootEl);
|
|
363
|
+
this.styles.set(style, style.textContent);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
}
|
|
368
|
+
// scripts
|
|
369
|
+
if (options?.scripts !== false) {
|
|
370
|
+
for (let path of shell.scripts) {
|
|
371
|
+
if (pathOffset)
|
|
372
|
+
path = path.slice(0, -pathOffset);
|
|
373
|
+
let script = Path.resolve(root, path);
|
|
374
|
+
eval(script.textContent)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
//#IFDEV
|
|
381
|
+
getParentNode() {
|
|
382
|
+
return this.startNode?.parentNode
|
|
383
|
+
}
|
|
415
384
|
|
|
416
385
|
get debug() {
|
|
417
386
|
return [
|
|
@@ -422,7 +391,7 @@ export default class NodeGroup {
|
|
|
422
391
|
|
|
423
392
|
let tree = nodeToArrayTree(item, nextNode => {
|
|
424
393
|
|
|
425
|
-
let path = this.paths.find(path=>path
|
|
394
|
+
let path = this.paths.find(path=>(path instanceof PathToNodes) && path.getNodes().includes(nextNode));
|
|
426
395
|
if (path)
|
|
427
396
|
return [`Path.nodes:`]
|
|
428
397
|
|
|
@@ -432,7 +401,7 @@ export default class NodeGroup {
|
|
|
432
401
|
// TODO: How to indend nodes belonging to a path vs those that just occur after the path?
|
|
433
402
|
return flattenAndIndent(tree)
|
|
434
403
|
}
|
|
435
|
-
else if (item instanceof
|
|
404
|
+
else if (item instanceof Path)
|
|
436
405
|
return setIndent(item.debug, 1)
|
|
437
406
|
}).flat(), 1)
|
|
438
407
|
]
|
|
@@ -472,81 +441,30 @@ export default class NodeGroup {
|
|
|
472
441
|
return true;
|
|
473
442
|
}
|
|
474
443
|
//#ENDIF
|
|
444
|
+
}
|
|
475
445
|
|
|
476
|
-
findStaticComponents(root, shell, startingPathDepth=0) {
|
|
477
|
-
let result = [];
|
|
478
|
-
|
|
479
|
-
// static components. These are WebComponents that do not have any constructor arguments that are expressions.
|
|
480
|
-
// Those are instead created by applyExpr() which calls applyComponentExprs() which calls instantiateComponent().
|
|
481
|
-
// Maybe someday these two paths will be merged?
|
|
482
|
-
// Must happen before ids because instantiateComponent will replace the element.
|
|
483
|
-
for (let path of shell.staticComponents) {
|
|
484
|
-
if (startingPathDepth)
|
|
485
|
-
path = path.slice(0, -startingPathDepth);
|
|
486
|
-
let el = resolveNodePath(root, path);
|
|
487
|
-
|
|
488
|
-
// Shell doesn't know if a web component is the pseudoRoot so we have to detect it here.
|
|
489
|
-
// Recreating it is necessary so we can pass the constructor args to it.
|
|
490
|
-
if (root !== el/* && !isReplaceEl(root, el)*/) // TODO: is isReplaceEl necessary?
|
|
491
|
-
result.push(el);
|
|
492
|
-
}
|
|
493
|
-
return result;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
instantiateStaticComponents(staticComponents) {
|
|
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);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* @param root {HTMLElement|DocumentFragment}
|
|
504
|
-
* @param shell {Shell}
|
|
505
|
-
* @param pathOffset {int} */
|
|
506
|
-
activateEmbeds(root, shell, pathOffset=0) {
|
|
507
|
-
|
|
508
|
-
let rootEl = this.rootNg.root;
|
|
509
|
-
if (rootEl) {
|
|
510
|
-
let options = this.rootNg.options;
|
|
511
|
-
|
|
512
|
-
// ids
|
|
513
|
-
if (options?.ids !== false) {
|
|
514
|
-
for (let path of shell.ids) {
|
|
515
|
-
if (pathOffset)
|
|
516
|
-
path = path.slice(0, -pathOffset);
|
|
517
|
-
let el = resolveNodePath(root, path);
|
|
518
|
-
Util.bindId(rootEl, el);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
446
|
|
|
522
|
-
// styles
|
|
523
|
-
if (options?.styles !== false) {
|
|
524
|
-
if (shell.styles.length)
|
|
525
|
-
this.styles = new Map();
|
|
526
|
-
for (let path of shell.styles) {
|
|
527
|
-
if (pathOffset)
|
|
528
|
-
path = path.slice(0, -pathOffset);
|
|
529
447
|
|
|
530
|
-
/** @type {HTMLStyleElement} */
|
|
531
|
-
let style = resolveNodePath(root, path);
|
|
532
|
-
if (rootEl.nodeType === 1) {
|
|
533
|
-
Util.bindStyles(style, rootEl);
|
|
534
|
-
this.styles.set(style, style.textContent);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
448
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
eval(script.textContent)
|
|
546
|
-
}
|
|
547
|
-
}
|
|
449
|
+
function getSingleEl(fragment) {
|
|
450
|
+
let nonempty = [];
|
|
451
|
+
for (let n of fragment.childNodes) {
|
|
452
|
+
if (n.nodeType === 1 || n.nodeType === 3 && n.textContent.trim().length) {
|
|
453
|
+
if (nonempty.length)
|
|
454
|
+
return null;
|
|
455
|
+
nonempty.push(n);
|
|
548
456
|
}
|
|
549
457
|
}
|
|
458
|
+
return nonempty[0];
|
|
550
459
|
}
|
|
551
460
|
|
|
552
|
-
|
|
461
|
+
/**
|
|
462
|
+
* Does the fragment have one child that's an element matching the tagname of el?
|
|
463
|
+
* @param fragment {DocumentFragment}
|
|
464
|
+
* @param tagName {string}
|
|
465
|
+
* @returns {boolean} */
|
|
466
|
+
function isReplaceEl(fragment, tagName) {
|
|
467
|
+
return fragment.children.length===1
|
|
468
|
+
&& tagName.includes('-')
|
|
469
|
+
&& fragment.children[0].tagName.replace('-SOLARITE-PLACEHOLDER', '') === tagName;
|
|
470
|
+
}
|