solarite 0.3.2 → 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 +2149 -2090
- package/dist/Solarite.js +1895 -1821
- package/dist/Solarite.min.js +2 -2
- package/package.json +12 -2
- package/readme.md +2 -2
- package/src/Globals.js +18 -24
- package/src/HtmlParser.js +1 -1
- package/src/MultiValueMap.js +3 -3
- package/src/NodeGroup.js +238 -483
- 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 +8 -0
- package/src/Shell.js +92 -83
- package/src/Solarite.d.ts +73 -29
- package/src/Solarite.js +133 -21
- package/src/Template.js +173 -42
- package/src/Util.js +74 -52
- package/src/assert.js +5 -5
- package/src/getArg.js +26 -24
- package/src/h.js +83 -128
- package/src/hash.js +11 -9
- package/src/toEl.js +83 -0
- package/src/udomdiff.js +0 -57
- package/src/watch.js +78 -81
- package/src/ExprPath.js +0 -1109
- package/src/createSolarite.js +0 -154
package/src/NodeGroup.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import ExprPath, {ExprPathType, resolveNodePath} from "./ExprPath.js";
|
|
3
|
-
import {getObjectHash} from "./hash.js";
|
|
4
|
-
import Shell from "./Shell.js";
|
|
1
|
+
import assert from "./assert.js";
|
|
5
2
|
import Util, {flattenAndIndent, nodeToArrayTree, setIndent} from "./Util.js";
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import
|
|
3
|
+
import Shell from "./Shell.js";
|
|
4
|
+
import RootNodeGroup from './RootNodeGroup.js';
|
|
5
|
+
import Path from "./Path.js";
|
|
6
|
+
import Globals from './Globals.js';
|
|
7
|
+
import PathToComponent from "./PathToComponent.js";
|
|
8
|
+
import PathToNodes from "./PathToNodes.js";
|
|
9
9
|
|
|
10
10
|
/** @typedef {boolean|string|number|function|Object|Array|Date|Node|Template} Expr */
|
|
11
11
|
|
|
@@ -14,17 +14,14 @@ import Globals from "./Globals.js";
|
|
|
14
14
|
*
|
|
15
15
|
* The range is determined by startNode and nodeMarker.
|
|
16
16
|
* startNode - never null. An empty text node is created before the first path if none exists.
|
|
17
|
-
* nodeMarker - null if this Nodegroup is at the end of its parents' nodes
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* */
|
|
17
|
+
* nodeMarker - null if this Nodegroup is at the end of its parents' nodes.*/
|
|
21
18
|
export default class NodeGroup {
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* @Type {RootNodeGroup} */
|
|
25
22
|
rootNg;
|
|
26
23
|
|
|
27
|
-
/** @type {
|
|
24
|
+
/** @type {Path} */
|
|
28
25
|
parentPath;
|
|
29
26
|
|
|
30
27
|
/** @type {Node|HTMLElement} First node of NodeGroup. Should never be null. */
|
|
@@ -32,10 +29,10 @@ export default class NodeGroup {
|
|
|
32
29
|
|
|
33
30
|
/** @type {Node|HTMLElement} A node that never changes that this NodeGroup should always insert its nodes before.
|
|
34
31
|
* An empty text node will be created to insertBefore if there's no other NodeMarker and this isn't at the last position.
|
|
35
|
-
* 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. */
|
|
36
33
|
endNode;
|
|
37
34
|
|
|
38
|
-
/** @type {
|
|
35
|
+
/** @type {Path[]} */
|
|
39
36
|
paths = [];
|
|
40
37
|
|
|
41
38
|
/** @type {string} Key that matches the template and the expressions. */
|
|
@@ -55,302 +52,220 @@ export default class NodeGroup {
|
|
|
55
52
|
* @type {?Map<HTMLStyleElement, string>} */
|
|
56
53
|
styles;
|
|
57
54
|
|
|
55
|
+
/** @type {Template} */
|
|
56
|
+
template;
|
|
58
57
|
|
|
59
58
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* @
|
|
63
|
-
|
|
64
|
-
if (!(this instanceof RootNodeGroup)) {
|
|
65
|
-
|
|
66
|
-
let [fragment, shell] = this.init(template, parentPath);
|
|
67
|
-
|
|
68
|
-
if (fragment && template.exprs.length) {
|
|
69
|
-
this.updatePaths(fragment, shell.paths);
|
|
70
|
-
|
|
71
|
-
// Static web components can sometimes have children created via expressions.
|
|
72
|
-
// 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);
|
|
75
|
-
|
|
76
|
-
this.activateEmbeds(fragment, shell);
|
|
59
|
+
* Root node at the top of the hierarchy.
|
|
60
|
+
* Should be moved to RootNodeGroup
|
|
61
|
+
* @type {HTMLElement} */
|
|
62
|
+
root;
|
|
77
63
|
|
|
78
|
-
// Apply exprs
|
|
79
|
-
this.applyExprs(template.exprs);
|
|
80
|
-
|
|
81
|
-
this.instantiateStaticComponents(staticComponents);
|
|
82
|
-
}
|
|
83
|
-
else if (shell)
|
|
84
|
-
this.activateEmbeds(fragment, shell);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
64
|
|
|
88
65
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
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.
|
|
91
68
|
* @param template {Template} Create it from the html strings and expressions in this template.
|
|
92
|
-
* @param parentPath {?
|
|
93
|
-
* @param
|
|
94
|
-
* @param
|
|
95
|
-
|
|
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;
|
|
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) {
|
|
101
73
|
this.rootNg = parentPath?.parentNg?.rootNg || this;
|
|
74
|
+
this.parentPath = parentPath;
|
|
102
75
|
|
|
103
76
|
/*#IFDEV*/assert(this.rootNg);/*#ENDIF*/
|
|
104
|
-
|
|
105
|
-
/** @type {Template} */
|
|
106
77
|
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.
|
|
78
|
+
this.closeKey = template.getCloseKey();
|
|
112
79
|
|
|
113
80
|
// 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
|
-
|
|
118
|
-
this.startNode = this.endNode = textNode;
|
|
119
|
-
return [];
|
|
81
|
+
if (template.isText) {
|
|
82
|
+
this.startNode = this.endNode = Globals.doc.createTextNode(template.html[0]);
|
|
120
83
|
}
|
|
121
|
-
else {
|
|
122
|
-
let shell = Shell.get(template.html);
|
|
123
|
-
let fragment = shell.fragment.cloneNode(true);
|
|
124
84
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
this.startNode = this.endNode = fragment;
|
|
132
|
-
}
|
|
133
|
-
return [fragment, shell];
|
|
134
|
-
}
|
|
135
|
-
}
|
|
85
|
+
else {
|
|
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);
|
|
136
89
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
applyExprs(exprs, paths=null) {
|
|
143
|
-
paths = paths || this.paths;
|
|
90
|
+
if (shellFragment.nodeType === 11) { // DocumentFragment
|
|
91
|
+
this.startNode = shellFragment.firstChild;
|
|
92
|
+
this.endNode = shellFragment.lastChild;
|
|
93
|
+
} else
|
|
94
|
+
this.startNode = this.endNode = shellFragment;
|
|
144
95
|
|
|
145
|
-
/*#IFDEV*/
|
|
146
|
-
this.verify();/*#ENDIF*/
|
|
147
96
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// 2. One component may need to use multiple attribute paths to be instantiated.
|
|
151
|
-
// 3. We apply them in reverse order so that a <select> box has its children created from an expression
|
|
152
|
-
// before its instantiated and its value attribute is set via an expression.
|
|
97
|
+
// Special setup for RootNodeGroup
|
|
98
|
+
if (this instanceof RootNodeGroup) {
|
|
153
99
|
|
|
154
|
-
let exprIndex = exprs.length - 1; // Update exprs at paths.
|
|
155
|
-
let lastComponentPathIndex;
|
|
156
|
-
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.
|
|
157
|
-
for (let i = paths.length - 1, path; path = paths[i]; i--) {
|
|
158
|
-
let prevPath = paths[i - 1];
|
|
159
|
-
let nextPath = paths[i + 1];
|
|
160
100
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
} else {
|
|
167
|
-
pathExprs[i] = [exprs[exprIndex]];
|
|
168
|
-
exprIndex--;
|
|
169
|
-
}
|
|
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');
|
|
170
106
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
107
|
+
this.root = el;
|
|
108
|
+
if (shellFragment.nodeValue.length)
|
|
109
|
+
this.root.append(shellFragment);
|
|
110
|
+
}
|
|
174
111
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
+
}
|
|
180
124
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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);
|
|
184
128
|
|
|
185
|
-
|
|
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);
|
|
186
133
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
componentProps[attrName] = pathExprs[j].length > 1 ? pathExprs[j].join('') : pathExprs[j][0];
|
|
191
|
-
}
|
|
134
|
+
// Go one level deeper into all of shell's paths.
|
|
135
|
+
startingPathDepth = 1;
|
|
136
|
+
}
|
|
192
137
|
|
|
193
|
-
|
|
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
|
+
}
|
|
194
143
|
|
|
195
|
-
// Set attributes on component.
|
|
196
|
-
for (let j=i; j<=lastComponentPathIndex; j++)
|
|
197
|
-
paths[j].apply(pathExprs[j]);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
144
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
}
|
|
204
164
|
|
|
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
|
+
}
|
|
205
172
|
|
|
206
|
-
|
|
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();
|
|
207
177
|
|
|
178
|
+
this.setPathsFromFragment(this.root, shell.paths, startingPathDepth);
|
|
179
|
+
this.activateEmbeds(this.root, shell, startingPathDepth);
|
|
180
|
+
}
|
|
181
|
+
this.startNode = this.endNode = this.root;
|
|
208
182
|
|
|
209
|
-
|
|
210
|
-
|
|
183
|
+
Globals.rootNodeGroups.set(this.root, this);
|
|
184
|
+
} // end if RootNodeGroup
|
|
211
185
|
|
|
186
|
+
else if (shell) {
|
|
187
|
+
if (shell.paths.length) {
|
|
188
|
+
this.setPathsFromFragment(shellFragment, shell.paths);
|
|
189
|
+
}
|
|
212
190
|
|
|
191
|
+
this.activateEmbeds(shellFragment, shell);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
213
194
|
|
|
214
|
-
|
|
215
|
-
this.
|
|
195
|
+
//#IFDEV
|
|
196
|
+
this.verify();
|
|
197
|
+
//#ENDIF
|
|
198
|
+
}
|
|
216
199
|
|
|
217
|
-
// If there's leftover expressions, there's probably an issue with the Shell that created this NodeGroup,
|
|
218
|
-
// and the number of paths not matching.
|
|
219
|
-
/*#IFDEV*/
|
|
220
|
-
assert(exprIndex === -1);/*#ENDIF*/
|
|
221
200
|
|
|
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) {
|
|
222
209
|
|
|
223
210
|
/*#IFDEV*/
|
|
224
|
-
this.verify()
|
|
225
|
-
|
|
211
|
+
this.verify();
|
|
212
|
+
/*#ENDIF*/
|
|
226
213
|
|
|
227
|
-
|
|
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
|
-
|
|
238
|
-
let isPreHtmlElement = el.hasAttribute('solarite-placeholder');
|
|
239
|
-
let isPreIsElement = el.hasAttribute('_is')
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
// Instantiate a placeholder.
|
|
243
|
-
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 = {};
|
|
253
|
-
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
|
-
//}
|
|
257
|
-
}
|
|
214
|
+
let paths = this.paths;
|
|
258
215
|
|
|
259
|
-
|
|
260
|
-
|
|
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;
|
|
261
228
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
instantiateComponent(el, isPreHtmlElement=undefined, props=undefined) {
|
|
273
|
-
if (isPreHtmlElement === undefined)
|
|
274
|
-
isPreHtmlElement = !el.hasAttribute('_is');
|
|
275
|
-
|
|
276
|
-
let tagName = (isPreHtmlElement
|
|
277
|
-
? el.tagName.slice(0, -21) // Remove -SOLARITE-PLACEHOLDER
|
|
278
|
-
: el.getAttribute('is')).toLowerCase();
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// Throw if custom element isn't defined.
|
|
282
|
-
let Constructor = customElements.get(tagName);
|
|
283
|
-
if (!Constructor)
|
|
284
|
-
throw new Error(`The custom tag name ${tagName} is not registered.`)
|
|
285
|
-
|
|
286
|
-
let attribs = {};
|
|
287
|
-
for (let name in props || {})
|
|
288
|
-
attribs[Util.dashesToCamel(name)] = props[name];
|
|
289
|
-
|
|
290
|
-
// Pass other attribs to constructor, since otherwise they're not yet set on the element,
|
|
291
|
-
// 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 (!attribs.hasOwnProperty(attribName) && attribName !== 'solarite-placeholder')
|
|
296
|
-
attribs[attribName] = attrib.value;
|
|
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);
|
|
297
239
|
}
|
|
240
|
+
else if (includeNonComponents)
|
|
241
|
+
path.apply(pathExprs[i]);
|
|
298
242
|
}
|
|
299
243
|
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
244
|
+
// If there's leftover expressions, there's probably an issue with the Shell that created this NodeGroup,
|
|
245
|
+
// and the number of paths not matching.
|
|
246
|
+
/*#IFDEV*/
|
|
247
|
+
assert(exprIndex === 0);
|
|
248
|
+
/*#ENDIF*/
|
|
304
249
|
|
|
305
|
-
if (!isPreHtmlElement)
|
|
306
|
-
newEl.setAttribute('is', el.getAttribute('is').toLowerCase());
|
|
307
250
|
|
|
308
|
-
|
|
309
|
-
el.replaceWith(newEl);
|
|
251
|
+
if (includeNonComponents) {
|
|
310
252
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (id)
|
|
314
|
-
delve(this.getRootNode(), id.split(/\./g), newEl);
|
|
253
|
+
// TODO: Only do this if we have Paths within styles?
|
|
254
|
+
this.updateStyles();
|
|
315
255
|
|
|
256
|
+
// Invalidate the nodes cache because we just changed it.
|
|
257
|
+
this.nodesCache = null;
|
|
316
258
|
|
|
317
|
-
// Update paths to use replaced element.
|
|
318
|
-
for (let path of this.paths) {
|
|
319
|
-
if (path.nodeMarker === el)
|
|
320
|
-
path.nodeMarker = newEl;
|
|
321
|
-
if (path.nodeBefore === el)
|
|
322
|
-
path.nodeBefore = newEl;
|
|
323
259
|
}
|
|
324
|
-
if (this.startNode === el)
|
|
325
|
-
this.startNode = newEl;
|
|
326
|
-
if (this.endNode === el)
|
|
327
|
-
this.endNode = newEl;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// applyComponentExprs() is called because we're rendering.
|
|
331
|
-
// So we want to render the sub-component also.
|
|
332
|
-
if (newEl.renderFirstTime)
|
|
333
|
-
newEl.renderFirstTime();
|
|
334
|
-
|
|
335
|
-
// Copy attributes over.
|
|
336
|
-
for (let attrib of el.attributes)
|
|
337
|
-
if (attrib.name !== '_is' && attrib.name !== 'solarite-placeholder')
|
|
338
|
-
newEl.setAttribute(attrib.name, attrib.value);
|
|
339
|
-
|
|
340
|
-
// Set dynamic attributes if they are primitive types.
|
|
341
|
-
for (let name in props) {
|
|
342
|
-
let val = props[name];
|
|
343
|
-
if (typeof val === 'boolean') {
|
|
344
|
-
if (val !== false && val !== undefined && val !== null)
|
|
345
|
-
newEl.setAttribute(name, '');
|
|
346
|
-
}
|
|
347
260
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
261
|
+
/*#IFDEV*/
|
|
262
|
+
this.verify();
|
|
263
|
+
/*#ENDIF*/
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// TODO: Give it a better name.
|
|
267
|
+
applyExprs2(exprs) {
|
|
352
268
|
|
|
353
|
-
return newEl;
|
|
354
269
|
}
|
|
355
270
|
|
|
356
271
|
/**
|
|
@@ -376,10 +291,6 @@ export default class NodeGroup {
|
|
|
376
291
|
return result;
|
|
377
292
|
}
|
|
378
293
|
|
|
379
|
-
getParentNode() {
|
|
380
|
-
return this.startNode?.parentNode
|
|
381
|
-
}
|
|
382
|
-
|
|
383
294
|
/**
|
|
384
295
|
* Get the root element of the NodeGroup's RootNodeGroup.
|
|
385
296
|
* @returns {HTMLElement|DocumentFragment} */
|
|
@@ -394,21 +305,15 @@ export default class NodeGroup {
|
|
|
394
305
|
}
|
|
395
306
|
|
|
396
307
|
/**
|
|
397
|
-
*
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
updatePaths(fragment, paths, offset) {
|
|
407
|
-
// Update paths to point to the fragment.
|
|
408
|
-
let pathLength = paths.length;
|
|
308
|
+
* Copy paths in fragment to this.paths.
|
|
309
|
+
* @param fragment {DocumentFragment|HTMLElement}
|
|
310
|
+
* @param paths
|
|
311
|
+
* @param startingPathDepth {int} */
|
|
312
|
+
setPathsFromFragment(fragment, paths, startingPathDepth=0) {
|
|
313
|
+
let pathLength = paths.length; // For faster iteration
|
|
409
314
|
this.paths.length = pathLength;
|
|
410
315
|
for (let i=0; i<pathLength; i++) {
|
|
411
|
-
let path = paths[i].clone(fragment,
|
|
316
|
+
let path = paths[i].clone(fragment, startingPathDepth)
|
|
412
317
|
path.parentNg = this;
|
|
413
318
|
this.paths[i] = path;
|
|
414
319
|
}
|
|
@@ -423,98 +328,6 @@ export default class NodeGroup {
|
|
|
423
328
|
}
|
|
424
329
|
}
|
|
425
330
|
|
|
426
|
-
//#IFDEV
|
|
427
|
-
/**
|
|
428
|
-
* @deprecated
|
|
429
|
-
* An interleaved array of sets of nodes and top-level ExprPaths
|
|
430
|
-
* @type {(Node|HTMLElement|ExprPath)[]} */
|
|
431
|
-
get nodes() { throw new Error('')};
|
|
432
|
-
|
|
433
|
-
get debug() {
|
|
434
|
-
return [
|
|
435
|
-
`parentNode: ${this.parentNode?.tagName?.toLowerCase()}`,
|
|
436
|
-
'nodes:',
|
|
437
|
-
...setIndent(this.getNodes().map(item => {
|
|
438
|
-
if (item instanceof Node) {
|
|
439
|
-
|
|
440
|
-
let tree = nodeToArrayTree(item, nextNode => {
|
|
441
|
-
|
|
442
|
-
let path = this.paths.find(path=>path.type === ExprPathType.Content && path.getNodes().includes(nextNode));
|
|
443
|
-
if (path)
|
|
444
|
-
return [`Path.nodes:`]
|
|
445
|
-
|
|
446
|
-
return [];
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
// TODO: How to indend nodes belonging to a path vs those that just occur after the path?
|
|
450
|
-
return flattenAndIndent(tree)
|
|
451
|
-
}
|
|
452
|
-
else if (item instanceof ExprPath)
|
|
453
|
-
return setIndent(item.debug, 1)
|
|
454
|
-
}).flat(), 1)
|
|
455
|
-
]
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
get debugNodes() { return this.getNodes() }
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
get debugNodesHtml() { return this.getNodes().map(n => n.outerHTML || n.textContent) }
|
|
462
|
-
|
|
463
|
-
verify() {
|
|
464
|
-
if (!window.verify)
|
|
465
|
-
return;
|
|
466
|
-
|
|
467
|
-
assert(this.startNode)
|
|
468
|
-
assert(this.endNode)
|
|
469
|
-
//assert(this.startNode !== this.endNode) // This can be true.
|
|
470
|
-
assert(this.startNode.parentNode === this.endNode.parentNode)
|
|
471
|
-
|
|
472
|
-
// Only if connected:
|
|
473
|
-
assert(!this.startNode.parentNode || this.startNode === this.endNode || this.startNode.compareDocumentPosition(this.endNode) === Node.DOCUMENT_POSITION_FOLLOWING)
|
|
474
|
-
|
|
475
|
-
// if (this.parentPath)
|
|
476
|
-
// assert(this.parentPath.nodeGroups.includes(this));
|
|
477
|
-
|
|
478
|
-
for (let path of this.paths) {
|
|
479
|
-
assert(path.parentNg === this)
|
|
480
|
-
|
|
481
|
-
// Fails for detached NodeGroups.
|
|
482
|
-
// NodeGroups get detached when their nodes are removed by udomdiff()
|
|
483
|
-
let parentNode = this.getParentNode();
|
|
484
|
-
if (parentNode)
|
|
485
|
-
assert(this.getParentNode().contains(path.getParentNode()))
|
|
486
|
-
path.verify();
|
|
487
|
-
// TODO: Make sure path nodes are all within our own node range.
|
|
488
|
-
}
|
|
489
|
-
return true;
|
|
490
|
-
}
|
|
491
|
-
//#ENDIF
|
|
492
|
-
|
|
493
|
-
findStaticComponents(root, shell, pathOffset=0) {
|
|
494
|
-
let result = [];
|
|
495
|
-
|
|
496
|
-
// static components. These are WebComponents that do not have any constructor arguments that are expressions.
|
|
497
|
-
// Those are instead created by applyExpr() which calls applyComponentExprs() which calls instantiateComponent().
|
|
498
|
-
// Maybe someday these two paths will be merged?
|
|
499
|
-
// Must happen before ids because instantiateComponent will replace the element.
|
|
500
|
-
for (let path of shell.staticComponents) {
|
|
501
|
-
if (pathOffset)
|
|
502
|
-
path = path.slice(0, -pathOffset);
|
|
503
|
-
let el = resolveNodePath(root, path);
|
|
504
|
-
|
|
505
|
-
// Shell doesn't know if a web component is the pseudoRoot so we have to detect it here.
|
|
506
|
-
// Recreating it is necessary so we can pass the constructor args to it.
|
|
507
|
-
if (root !== el/* && !isReplaceEl(root, el)*/) // TODO: is isReplaceEl necessary?
|
|
508
|
-
result.push(el);
|
|
509
|
-
}
|
|
510
|
-
return result;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
instantiateStaticComponents(staticComponents) {
|
|
514
|
-
for (let el of staticComponents)
|
|
515
|
-
this.instantiateComponent(el)
|
|
516
|
-
}
|
|
517
|
-
|
|
518
331
|
/**
|
|
519
332
|
* @param root {HTMLElement|DocumentFragment}
|
|
520
333
|
* @param shell {Shell}
|
|
@@ -530,10 +343,10 @@ export default class NodeGroup {
|
|
|
530
343
|
for (let path of shell.ids) {
|
|
531
344
|
if (pathOffset)
|
|
532
345
|
path = path.slice(0, -pathOffset);
|
|
533
|
-
let el =
|
|
346
|
+
let el = Path.resolve(root, path);
|
|
534
347
|
Util.bindId(rootEl, el);
|
|
535
348
|
}
|
|
536
|
-
|
|
349
|
+
}
|
|
537
350
|
|
|
538
351
|
// styles
|
|
539
352
|
if (options?.styles !== false) {
|
|
@@ -544,7 +357,7 @@ export default class NodeGroup {
|
|
|
544
357
|
path = path.slice(0, -pathOffset);
|
|
545
358
|
|
|
546
359
|
/** @type {HTMLStyleElement} */
|
|
547
|
-
let style =
|
|
360
|
+
let style = Path.resolve(root, path);
|
|
548
361
|
if (rootEl.nodeType === 1) {
|
|
549
362
|
Util.bindStyles(style, rootEl);
|
|
550
363
|
this.styles.set(style, style.textContent);
|
|
@@ -557,140 +370,82 @@ export default class NodeGroup {
|
|
|
557
370
|
for (let path of shell.scripts) {
|
|
558
371
|
if (pathOffset)
|
|
559
372
|
path = path.slice(0, -pathOffset);
|
|
560
|
-
let script =
|
|
373
|
+
let script = Path.resolve(root, path);
|
|
561
374
|
eval(script.textContent)
|
|
562
375
|
}
|
|
563
376
|
}
|
|
564
377
|
}
|
|
565
378
|
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
export class RootNodeGroup extends NodeGroup {
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
* Root node at the top of the hierarchy.
|
|
573
|
-
* @type {HTMLElement} */
|
|
574
|
-
root;
|
|
575
379
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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 {
|
|
380
|
+
//#IFDEV
|
|
381
|
+
getParentNode() {
|
|
382
|
+
return this.startNode?.parentNode
|
|
383
|
+
}
|
|
607
384
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
// Save slot children
|
|
615
|
-
let slotChildren;
|
|
616
|
-
if (el.childNodes.length) {
|
|
617
|
-
slotChildren = document.createDocumentFragment();
|
|
618
|
-
slotChildren.append(...el.childNodes);
|
|
619
|
-
}
|
|
385
|
+
get debug() {
|
|
386
|
+
return [
|
|
387
|
+
`parentNode: ${this.parentNode?.tagName?.toLowerCase()}`,
|
|
388
|
+
'nodes:',
|
|
389
|
+
...setIndent(this.getNodes().map(item => {
|
|
390
|
+
if (item?.nodeType) {
|
|
620
391
|
|
|
621
|
-
|
|
392
|
+
let tree = nodeToArrayTree(item, nextNode => {
|
|
622
393
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
394
|
+
let path = this.paths.find(path=>(path instanceof PathToNodes) && path.getNodes().includes(nextNode));
|
|
395
|
+
if (path)
|
|
396
|
+
return [`Path.nodes:`]
|
|
626
397
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (!el.hasAttribute(attrib.name) && attrib.name !== 'solarite-placeholder')
|
|
630
|
-
el.setAttribute(attrib.name, attrib.value);
|
|
398
|
+
return [];
|
|
399
|
+
})
|
|
631
400
|
|
|
632
|
-
//
|
|
633
|
-
|
|
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);
|
|
401
|
+
// TODO: How to indend nodes belonging to a path vs those that just occur after the path?
|
|
402
|
+
return flattenAndIndent(tree)
|
|
638
403
|
}
|
|
404
|
+
else if (item instanceof Path)
|
|
405
|
+
return setIndent(item.debug, 1)
|
|
406
|
+
}).flat(), 1)
|
|
407
|
+
]
|
|
408
|
+
}
|
|
639
409
|
|
|
640
|
-
|
|
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
|
-
}
|
|
410
|
+
get debugNodes() { return this.getNodes() }
|
|
661
411
|
|
|
662
|
-
root = el;
|
|
663
412
|
|
|
664
|
-
|
|
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.
|
|
413
|
+
get debugNodesHtml() { return this.getNodes().map(n => n.outerHTML || n.textContent) }
|
|
669
414
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
offset = 1;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
415
|
+
verify() {
|
|
416
|
+
if (!window.verify)
|
|
417
|
+
return;
|
|
676
418
|
|
|
677
|
-
|
|
419
|
+
assert(this.startNode)
|
|
420
|
+
assert(this.endNode)
|
|
421
|
+
//assert(this.startNode !== this.endNode) // This can be true.
|
|
422
|
+
assert(this.startNode.parentNode === this.endNode.parentNode)
|
|
678
423
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
// So we find them first, then call activateStaticComponents() after their children have been created.
|
|
682
|
-
let staticComponents = this.findStaticComponents(root, shell, offset);
|
|
424
|
+
// Only if connected:
|
|
425
|
+
assert(!this.startNode.parentNode || this.startNode === this.endNode || this.startNode.compareDocumentPosition(this.endNode) === Node.DOCUMENT_POSITION_FOLLOWING)
|
|
683
426
|
|
|
684
|
-
|
|
427
|
+
// if (this.parentPath)
|
|
428
|
+
// assert(this.parentPath.nodeGroups.includes(this));
|
|
685
429
|
|
|
686
|
-
|
|
687
|
-
|
|
430
|
+
for (let path of this.paths) {
|
|
431
|
+
assert(path.parentNg === this)
|
|
688
432
|
|
|
689
|
-
|
|
433
|
+
// Fails for detached NodeGroups.
|
|
434
|
+
// NodeGroups get detached when their nodes are removed by udomdiff()
|
|
435
|
+
let parentNode = this.getParentNode();
|
|
436
|
+
if (parentNode)
|
|
437
|
+
assert(this.getParentNode().contains(path.getParentNode()))
|
|
438
|
+
path.verify();
|
|
439
|
+
// TODO: Make sure path nodes are all within our own node range.
|
|
690
440
|
}
|
|
441
|
+
return true;
|
|
691
442
|
}
|
|
443
|
+
//#ENDIF
|
|
692
444
|
}
|
|
693
445
|
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
|
|
694
449
|
function getSingleEl(fragment) {
|
|
695
450
|
let nonempty = [];
|
|
696
451
|
for (let n of fragment.childNodes) {
|
|
@@ -706,10 +461,10 @@ function getSingleEl(fragment) {
|
|
|
706
461
|
/**
|
|
707
462
|
* Does the fragment have one child that's an element matching the tagname of el?
|
|
708
463
|
* @param fragment {DocumentFragment}
|
|
709
|
-
* @param
|
|
464
|
+
* @param tagName {string}
|
|
710
465
|
* @returns {boolean} */
|
|
711
|
-
function isReplaceEl(fragment,
|
|
466
|
+
function isReplaceEl(fragment, tagName) {
|
|
712
467
|
return fragment.children.length===1
|
|
713
|
-
&&
|
|
714
|
-
&& fragment.children[0].tagName.replace('-SOLARITE-PLACEHOLDER', '') ===
|
|
468
|
+
&& tagName.includes('-')
|
|
469
|
+
&& fragment.children[0].tagName.replace('-SOLARITE-PLACEHOLDER', '') === tagName;
|
|
715
470
|
}
|