solarite 0.1.0 → 0.1.1
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/build/build.bat +1 -0
- package/deno.lock +176 -0
- package/dist/Solarite-debug.js +456 -1118
- package/dist/Solarite.js +438 -1216
- package/dist/Solarite.min.js +2 -2
- package/docs/index.md +366 -216
- package/docs/js/codemirror/themeSolarIce.js +1 -1
- package/docs/js/documentation.js +1 -1
- package/docs/js/ui/FlexResizer.js +1 -2
- package/docs/media/documentation.css +2 -2
- package/index.html +40 -23
- package/package.json +1 -1
- package/readme.md +11 -1
- package/src/solarite/ExprPath.js +92 -3
- package/src/solarite/Globals.js +11 -0
- package/src/solarite/MultiValueMap.js +5 -1
- package/src/solarite/NodeGroup.js +54 -124
- package/src/solarite/NodeGroupManager.js +15 -125
- package/src/solarite/Shell.js +5 -5
- package/src/solarite/Solarite.js +5 -2
- package/src/solarite/Template.js +131 -8
- package/src/solarite/createSolarite.js +18 -6
- package/src/solarite/getArg.js +11 -11
- package/src/solarite/hash.js +1 -1
- package/src/solarite/r.js +24 -14
- package/src/solarite/watch.js +1 -1
- package/src/solarite/watch2.js +3 -2
- package/src/solarite/watch3.js +91 -0
- package/src/unused/onConnect.js +79 -0
- package/tests/Solarite.test.js +281 -34
- package/tests/Testimony.js +274 -67
- package/tests/index.html +3 -34
- package/tests/run.bat +2 -0
- package/tests/NodeGroup.test.js +0 -115
- package/tests/Shell.test.js +0 -75
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
# Solarite
|
|
2
2
|
|
|
3
|
-
Solarite is a small (10KB min+gzip), fast, compilation-free JavaScript web component library
|
|
3
|
+
Solarite is a small (10KB min+gzip), fast, compilation-free JavaScript web component library for rendering minimal updates. It follows modern web standards and integrates well with vanilla JavaScript.
|
|
4
|
+
|
|
5
|
+
It's currently in beta.
|
|
6
|
+
|
|
7
|
+
For documentation and examples, see the **[Official Solarite Page](https://vorticode.github.io/solarite/)**.
|
|
8
|
+
|
|
9
|
+
Download [Solarite.js](https://vorticode.github.io/solarite/dist/Solarite.js) (87KB, 8KB min+gzip)
|
|
10
|
+
|
|
11
|
+
Or:
|
|
12
|
+
|
|
13
|
+
`npm install solarite`
|
package/src/solarite/ExprPath.js
CHANGED
|
@@ -3,6 +3,8 @@ import delve from "../util/delve.js";
|
|
|
3
3
|
import {getObjectId} from "./hash.js";
|
|
4
4
|
import NodeGroup from "./NodeGroup.js";
|
|
5
5
|
import {div, findArrayDiff, setIndent} from "./Util.js";
|
|
6
|
+
import Template from "./Template.js";
|
|
7
|
+
import Globals from "./Globals.js";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Path to where an expression should be evaluated within a Shell.
|
|
@@ -122,6 +124,94 @@ export default class ExprPath {
|
|
|
122
124
|
this.attrNames = new Set();
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
/**
|
|
128
|
+
*
|
|
129
|
+
* @param expr {Template|Node|Array|function|*}
|
|
130
|
+
* @param newNodes {(Node|Template)[]}
|
|
131
|
+
* @param secondPass {Array} Locations within newNodes to evaluate later. */
|
|
132
|
+
apply(expr, newNodes, secondPass) {
|
|
133
|
+
|
|
134
|
+
if (expr instanceof Template) {
|
|
135
|
+
expr.nodegroup = this.parentNg; // All tests pass w/o this.
|
|
136
|
+
|
|
137
|
+
let ng = this.parentNg.manager.getNodeGroup(expr, true);
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if (ng) {
|
|
141
|
+
//#IFDEV
|
|
142
|
+
// Make sure the nodeCache of the ExprPath we took it from is sitll valid.
|
|
143
|
+
if (ng.parentPath)
|
|
144
|
+
ng.parentPath.verify();
|
|
145
|
+
//#ENDIF
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
// TODO: Track ranges of changed nodes and only pass those to udomdiff?
|
|
149
|
+
// But will that break the swap benchmark?
|
|
150
|
+
newNodes.push(...ng.getNodes());
|
|
151
|
+
this.nodeGroups.push(ng);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// If expression, evaluate later to find partial match.
|
|
155
|
+
else {
|
|
156
|
+
secondPass.push([newNodes.length, this.nodeGroups.length])
|
|
157
|
+
newNodes.push(expr)
|
|
158
|
+
this.nodeGroups.push(null); // placeholder
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Node created by an expression.
|
|
163
|
+
else if (expr instanceof Node) {
|
|
164
|
+
|
|
165
|
+
// DocumentFragment created by an expression.
|
|
166
|
+
if (expr instanceof DocumentFragment)
|
|
167
|
+
newNodes.push(...expr.childNodes);
|
|
168
|
+
else
|
|
169
|
+
newNodes.push(expr);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
else if (Array.isArray(expr))
|
|
173
|
+
for (let subExpr of expr)
|
|
174
|
+
this.apply(subExpr, newNodes, secondPass)
|
|
175
|
+
|
|
176
|
+
else if (typeof expr === 'function') {
|
|
177
|
+
//expr = watchFunction(expr, this.parentNg.manager); // part of watch2() experiment.
|
|
178
|
+
|
|
179
|
+
Globals.currentExprPath = [this, expr]; // Used by watch3()
|
|
180
|
+
let result = expr();
|
|
181
|
+
Globals.currentExprPath = null;
|
|
182
|
+
|
|
183
|
+
this.apply(result, newNodes, secondPass)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Text
|
|
187
|
+
else {
|
|
188
|
+
// Convert falsy values (but not 0) to empty string.
|
|
189
|
+
// Convert numbers to string so they compare the same.
|
|
190
|
+
let text = (expr === undefined || expr === false || expr === null) ? '' : expr + '';
|
|
191
|
+
|
|
192
|
+
// Fast path for updating the text of a single text node.
|
|
193
|
+
let first = this.nodeBefore.nextSibling;
|
|
194
|
+
if (first.nodeType === 3 && first.nextSibling === this.nodeMarker && !newNodes.includes(first)) {
|
|
195
|
+
if (first.textContent !== text)
|
|
196
|
+
first.textContent = text;
|
|
197
|
+
|
|
198
|
+
newNodes.push(first);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
else {
|
|
202
|
+
// TODO: Optimize this into a Set or Map or something?
|
|
203
|
+
if (!this.existingTextNodes)
|
|
204
|
+
this.existingTextNodes = this.getNodes().filter(n => n.nodeType === 3);
|
|
205
|
+
|
|
206
|
+
let idx = this.existingTextNodes.findIndex(n => n.textContent === text);
|
|
207
|
+
if (idx !== -1)
|
|
208
|
+
newNodes.push(...this.existingTextNodes.splice(idx, 1))
|
|
209
|
+
else
|
|
210
|
+
newNodes.push(this.parentNode.ownerDocument.createTextNode(text));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
125
215
|
applyMultipleAttribs(node, expr) {
|
|
126
216
|
/*#IFDEV*/assert(this.type === PathType.Multiple);/*#ENDIF*/
|
|
127
217
|
|
|
@@ -302,8 +392,7 @@ export default class ExprPath {
|
|
|
302
392
|
* Clear the nodeCache of this ExprPath, as well as all parent and child ExprPaths that
|
|
303
393
|
* share the same DOM parent node.
|
|
304
394
|
*
|
|
305
|
-
* TODO: Is recursive clearing ever necessary?
|
|
306
|
-
*/
|
|
395
|
+
* TODO: Is recursive clearing ever necessary? */
|
|
307
396
|
clearNodesCache() {
|
|
308
397
|
let path = this;
|
|
309
398
|
|
|
@@ -321,7 +410,7 @@ export default class ExprPath {
|
|
|
321
410
|
|
|
322
411
|
// Clear cache of child ExprPaths that have the same parentNode
|
|
323
412
|
for (let ng of path.nodeGroups) {
|
|
324
|
-
if (ng) // Can be null from
|
|
413
|
+
if (ng) // Can be null from apply()'s push(null) call.
|
|
325
414
|
for (let path2 of ng.paths) {
|
|
326
415
|
if (path2.type === PathType.Content && path2.parentNode === parentNode) {
|
|
327
416
|
path2.nodesCache = null;
|
|
@@ -19,7 +19,11 @@ export default class MultiValueMap {
|
|
|
19
19
|
return this.data[key] || [];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Remove one value from a key, and return it.
|
|
24
|
+
* @param key {string}
|
|
25
|
+
* @param val If specified, make sure we delete this specific value, if a key exists more than once.
|
|
26
|
+
* @returns {*} */
|
|
23
27
|
delete(key, val=undefined) {
|
|
24
28
|
// if (key === '["Html2",[[["Html3",["F1","A"]],["Html3",["F1","B"]]]]]')
|
|
25
29
|
// debugger;
|
|
@@ -2,12 +2,11 @@ import {assert} from "../util/Errors.js";
|
|
|
2
2
|
import ExprPath, {PathType, resolveNodePath} from "./ExprPath.js";
|
|
3
3
|
import {getObjectHash} from "./hash.js";
|
|
4
4
|
import Shell from "./Shell.js";
|
|
5
|
-
import Template from "./Template.js";
|
|
6
5
|
import udomdiff from "./udomdiff.js";
|
|
7
|
-
import {watchFunction} from './watch2.js';
|
|
8
6
|
import Util, {div, findArrayDiff, flattenAndIndent, isEvent, nodeToArrayTree, setIndent} from "./Util.js";
|
|
9
7
|
import NodeGroupManager from "./NodeGroupManager.js";
|
|
10
8
|
|
|
9
|
+
|
|
11
10
|
/** @typedef {boolean|string|number|function|Object|Array|Date|Node} Expr */
|
|
12
11
|
|
|
13
12
|
/**
|
|
@@ -37,10 +36,10 @@ export default class NodeGroup {
|
|
|
37
36
|
/** @type {ExprPath[]} */
|
|
38
37
|
paths = [];
|
|
39
38
|
|
|
40
|
-
/** @type {string} */
|
|
39
|
+
/** @type {string} Key that matches the template and the expressions. */
|
|
41
40
|
exactKey;
|
|
42
41
|
|
|
43
|
-
/** @type {string} */
|
|
42
|
+
/** @type {string} Key that only matches the template. */
|
|
44
43
|
closeKey;
|
|
45
44
|
|
|
46
45
|
/** @type {boolean} Used by NodeGroupManager. */
|
|
@@ -73,8 +72,10 @@ export default class NodeGroup {
|
|
|
73
72
|
* @returns {NodeGroup} */
|
|
74
73
|
constructor(template, manager=null) {
|
|
75
74
|
|
|
76
|
-
|
|
75
|
+
/** @type {Template} */
|
|
77
76
|
this.template = template;
|
|
77
|
+
|
|
78
|
+
/** @type {NodeGroupManager} */
|
|
78
79
|
this.manager = manager;
|
|
79
80
|
|
|
80
81
|
// new!
|
|
@@ -90,7 +91,7 @@ export default class NodeGroup {
|
|
|
90
91
|
let replaceMode = typeof template.replaceMode === 'boolean'
|
|
91
92
|
? template.replaceMode
|
|
92
93
|
: fragment.children.length===1 &&
|
|
93
|
-
fragment.firstElementChild?.tagName.replace(/-
|
|
94
|
+
fragment.firstElementChild?.tagName.replace(/-SOLARITE-PLACEHOLDER$/, '')
|
|
94
95
|
=== manager?.rootEl?.tagName
|
|
95
96
|
if (replaceMode) {
|
|
96
97
|
this.pseudoRoot = fragment.firstElementChild;
|
|
@@ -117,7 +118,7 @@ export default class NodeGroup {
|
|
|
117
118
|
|
|
118
119
|
|
|
119
120
|
// Update web component placeholders.
|
|
120
|
-
// Ctrl+F "
|
|
121
|
+
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
121
122
|
// Is this list needed at all?
|
|
122
123
|
//for (let component of shell.components)
|
|
123
124
|
// this.components.push(resolveNodePath(this.startNode.parentNode, getNodePath(component)))
|
|
@@ -150,13 +151,19 @@ export default class NodeGroup {
|
|
|
150
151
|
this.createNewComponent(el)
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
if (this.manager
|
|
154
|
+
if (this.manager?.rootEl) {
|
|
154
155
|
|
|
155
156
|
// ids
|
|
156
157
|
if (this.manager.options.ids !== false)
|
|
157
158
|
for (let path of shell.ids) {
|
|
158
159
|
let el = resolveNodePath(root, path);
|
|
159
|
-
let id = el.getAttribute('data-id') || el.getAttribute('id')
|
|
160
|
+
let id = el.getAttribute('data-id') || el.getAttribute('id');
|
|
161
|
+
|
|
162
|
+
// Don't allow overwriting existing class properties if they already have a non-Node value.
|
|
163
|
+
if (this.manager.rootEl[id] && !(this.manager.rootEl[id] instanceof Node))
|
|
164
|
+
throw new Error(`${this.manager.rootEl.constructor.name}.${id} already has a value. `+
|
|
165
|
+
`Can't set it as a reference to <${el.tagName.toLowerCase()} id="${id}">`);
|
|
166
|
+
|
|
160
167
|
this.manager.rootEl[id] = el;
|
|
161
168
|
}
|
|
162
169
|
|
|
@@ -181,20 +188,13 @@ export default class NodeGroup {
|
|
|
181
188
|
}
|
|
182
189
|
}
|
|
183
190
|
|
|
184
|
-
updateStyles() {
|
|
185
|
-
if (this.styles)
|
|
186
|
-
for (let [style, oldText] of this.styles) {
|
|
187
|
-
let newText = style.textContent;
|
|
188
|
-
if (oldText !== newText)
|
|
189
|
-
Util.bindStyles(style, this.manager.rootEl);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
191
|
/**
|
|
194
192
|
* Use the paths to insert the given expressions.
|
|
195
193
|
* Dispatches expression handling to other functions depending on the path type.
|
|
196
|
-
* @param exprs {(*|*[]|function|Template)[]}
|
|
197
|
-
|
|
194
|
+
* @param exprs {(*|*[]|function|Template)[]}
|
|
195
|
+
* @param paths {?ExprPath[]} Optional. */
|
|
196
|
+
applyExprs(exprs, paths=null) {
|
|
197
|
+
paths = paths || this.paths;
|
|
198
198
|
|
|
199
199
|
/*#IFDEV*/this.verify();/*#ENDIF*/
|
|
200
200
|
// Update exprs at paths.
|
|
@@ -202,7 +202,7 @@ export default class NodeGroup {
|
|
|
202
202
|
|
|
203
203
|
// We apply them in reverse order so that a <select> box has its options created from an expression
|
|
204
204
|
// before its value attribute is set via an expression.
|
|
205
|
-
for (let path of
|
|
205
|
+
for (let path of paths.toReversed()) {
|
|
206
206
|
expr = exprs[exprIndex];
|
|
207
207
|
|
|
208
208
|
// Nodes
|
|
@@ -213,8 +213,8 @@ export default class NodeGroup {
|
|
|
213
213
|
|
|
214
214
|
// Attributes
|
|
215
215
|
else {
|
|
216
|
-
let node = path.nodeMarker;
|
|
217
|
-
let
|
|
216
|
+
let node = path.nodeMarker;
|
|
217
|
+
let el = (this.manager?.rootEl && node === this.pseudoRoot) ? this.manager.rootEl : node;
|
|
218
218
|
/*#IFDEV*/assert(node);/*#ENDIF*/
|
|
219
219
|
|
|
220
220
|
// This is necessary both here and below.
|
|
@@ -224,10 +224,10 @@ export default class NodeGroup {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
if (path.type === PathType.Multiple)
|
|
227
|
-
path.applyMultipleAttribs(
|
|
227
|
+
path.applyMultipleAttribs(el, expr)
|
|
228
228
|
|
|
229
229
|
// Capture attribute expressions to later send to the constructor of a web component.
|
|
230
|
-
// Ctrl+F "
|
|
230
|
+
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
231
231
|
else if (path.nodeMarker !== this.pseudoRoot && path.type === PathType.Component)
|
|
232
232
|
this.currentComponentProps[path.attrName] = expr;
|
|
233
233
|
|
|
@@ -238,13 +238,13 @@ export default class NodeGroup {
|
|
|
238
238
|
|
|
239
239
|
// Event attribute value
|
|
240
240
|
if (path.attrValue===null && (typeof expr === 'function' || Array.isArray(expr)) && isEvent(path.attrName)) {
|
|
241
|
-
let root = this.manager
|
|
242
|
-
path.applyEventAttrib(
|
|
241
|
+
let root = this.manager?.rootEl || this.startNode.parentNode; // latter is used when constructing a whole element.
|
|
242
|
+
path.applyEventAttrib(el, expr, root);
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
// Regular attribute value.
|
|
246
|
-
else
|
|
247
|
-
exprIndex = path.applyValueAttrib(
|
|
246
|
+
else // One node value may have multiple expressions. Here we apply them all at once.
|
|
247
|
+
exprIndex = path.applyValueAttrib(el, exprs, exprIndex);
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
lastNode = path.nodeMarker;
|
|
@@ -273,6 +273,10 @@ export default class NodeGroup {
|
|
|
273
273
|
/*#IFDEV*/this.verify();/*#ENDIF*/
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
applyExpr(path, expr) {
|
|
277
|
+
// TODO: Use this if I can figure out how to adapt applyValueAttrib() to it.
|
|
278
|
+
}
|
|
279
|
+
|
|
276
280
|
/**
|
|
277
281
|
* Insert/replace the nodes created by a single expression.
|
|
278
282
|
* Called by applyExprs()
|
|
@@ -294,7 +298,7 @@ export default class NodeGroup {
|
|
|
294
298
|
//for (let ng of path.nodeGroups) // TODO: Is this necessary?
|
|
295
299
|
// ng.parentPath = null;
|
|
296
300
|
path.nodeGroups = [];
|
|
297
|
-
|
|
301
|
+
path.apply(expr, newNodes, secondPass);
|
|
298
302
|
this.existingTextNodes = null;
|
|
299
303
|
|
|
300
304
|
// TODO: Create an array of old vs Nodes and NodeGroups together.
|
|
@@ -384,117 +388,32 @@ export default class NodeGroup {
|
|
|
384
388
|
}
|
|
385
389
|
}
|
|
386
390
|
|
|
387
|
-
// TODO: Move to ExprPath?
|
|
388
|
-
applyOneExpr(expr, path, newNodes, secondPass) {
|
|
389
|
-
|
|
390
|
-
if (expr instanceof Template) {
|
|
391
|
-
expr.parentPath = path;
|
|
392
|
-
expr.nodegroup = this;
|
|
393
|
-
|
|
394
|
-
//if (window.debug && expr.exprs[0] === 'Banana' && path.nodeGroups.length === 0)
|
|
395
|
-
//if (window.debug && expr.exprs[0] === 'Banana')
|
|
396
|
-
// debugger;
|
|
397
|
-
|
|
398
|
-
let ng = this.manager.getNodeGroup(expr, true);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (ng) {
|
|
402
|
-
//#IFDEV
|
|
403
|
-
// Make sure the nodeCache of the ExprPath we took it from is sitll valid.
|
|
404
|
-
if (ng.parentPath)
|
|
405
|
-
ng.parentPath.verify();
|
|
406
|
-
//#ENDIF
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
// TODO: Track ranges of changed nodes and only pass those to udomdiff?
|
|
410
|
-
// But will that break the swap benchmark?
|
|
411
|
-
newNodes.push(...ng.getNodes());
|
|
412
|
-
path.nodeGroups.push(ng);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// If expression, evaluate later to find partial match.
|
|
416
|
-
else {
|
|
417
|
-
secondPass.push([newNodes.length, path.nodeGroups.length])
|
|
418
|
-
newNodes.push(expr)
|
|
419
|
-
path.nodeGroups.push(null); // placeholder
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Node created by an expression.
|
|
424
|
-
else if (expr instanceof Node) {
|
|
425
|
-
|
|
426
|
-
// DocumentFragment created by an expression.
|
|
427
|
-
if (expr instanceof DocumentFragment)
|
|
428
|
-
newNodes.push(...expr.childNodes);
|
|
429
|
-
else
|
|
430
|
-
newNodes.push(expr);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
else if (Array.isArray(expr))
|
|
434
|
-
for (let subExpr of expr)
|
|
435
|
-
this.applyOneExpr(subExpr, path, newNodes, secondPass)
|
|
436
|
-
|
|
437
|
-
else if (typeof expr === 'function') {
|
|
438
|
-
expr = watchFunction(expr, this.manager)
|
|
439
|
-
|
|
440
|
-
this.applyOneExpr(expr, path, newNodes, secondPass)
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Text
|
|
444
|
-
else {
|
|
445
|
-
// Convert falsy values (but not 0) to empty string.
|
|
446
|
-
// Convert numbers to string so they compare the same.
|
|
447
|
-
let text = (expr === undefined || expr === false || expr === null) ? '' : expr + '';
|
|
448
|
-
|
|
449
|
-
// Fast path for updating the text of a single text node.
|
|
450
|
-
let first = path.nodeBefore.nextSibling;
|
|
451
|
-
if (first.nodeType === 3 && first.nextSibling === path.nodeMarker && !newNodes.includes(first)) {
|
|
452
|
-
if (first.textContent !== text)
|
|
453
|
-
first.textContent = text;
|
|
454
|
-
|
|
455
|
-
newNodes.push(first);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
else {
|
|
459
|
-
// TODO: Optimize this into a Set or Map or something?
|
|
460
|
-
if (!this.existingTextNodes)
|
|
461
|
-
this.existingTextNodes = path.getNodes().filter(n => n.nodeType === 3);
|
|
462
|
-
|
|
463
|
-
let idx = this.existingTextNodes.findIndex(n => n.textContent === text);
|
|
464
|
-
if (idx !== -1)
|
|
465
|
-
newNodes.push(...this.existingTextNodes.splice(idx, 1))
|
|
466
|
-
else
|
|
467
|
-
newNodes.push(path.parentNode.ownerDocument.createTextNode(text));
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
391
|
/**
|
|
473
392
|
* Create a nested RedComponent or call render with the new props.
|
|
474
393
|
* @param el {Solarite:HTMLElement}
|
|
475
394
|
* @param props {Object} */
|
|
476
395
|
applyComponentExprs(el, props) {
|
|
477
|
-
|
|
396
|
+
|
|
478
397
|
// TODO: Does a hash of this already exist somewhere?
|
|
479
398
|
// Perhaps if Components were treated as child NodeGroups, which would need to be the child of an ExprPath,
|
|
480
399
|
// then we could re-use the hash and logic from NodeManager?
|
|
481
400
|
let newHash = getObjectHash(props);
|
|
482
|
-
|
|
483
|
-
let isPreHtmlElement = el.tagName.endsWith('-
|
|
401
|
+
|
|
402
|
+
let isPreHtmlElement = el.tagName.endsWith('-SOLARITE-PLACEHOLDER');
|
|
484
403
|
let isPreIsElement = el.hasAttribute('_is')
|
|
485
|
-
|
|
486
|
-
|
|
404
|
+
|
|
405
|
+
|
|
487
406
|
// Instantiate a placeholder.
|
|
488
407
|
if (isPreHtmlElement || isPreIsElement)
|
|
489
408
|
el = this.createNewComponent(el, isPreHtmlElement, props);
|
|
490
|
-
|
|
409
|
+
|
|
491
410
|
// Update params of placeholder.
|
|
492
411
|
else if (el.render) {
|
|
493
412
|
let oldHash = componentHash.get(el);
|
|
494
413
|
if (oldHash !== newHash)
|
|
495
414
|
el.render(props); // Pass new values of props to render so it can decide how it wants to respond.
|
|
496
415
|
}
|
|
497
|
-
|
|
416
|
+
|
|
498
417
|
componentHash.set(el, newHash);
|
|
499
418
|
}
|
|
500
419
|
|
|
@@ -513,8 +432,8 @@ export default class NodeGroup {
|
|
|
513
432
|
isPreHtmlElement = !el.hasAttribute('_is');
|
|
514
433
|
|
|
515
434
|
let tagName = (isPreHtmlElement
|
|
516
|
-
? el.tagName.endsWith('-
|
|
517
|
-
? el.tagName.slice(0, -
|
|
435
|
+
? el.tagName.endsWith('-SOLARITE-PLACEHOLDER')
|
|
436
|
+
? el.tagName.slice(0, -21)
|
|
518
437
|
: el.tagName
|
|
519
438
|
: el.getAttribute('is')).toLowerCase();
|
|
520
439
|
|
|
@@ -634,6 +553,17 @@ export default class NodeGroup {
|
|
|
634
553
|
}
|
|
635
554
|
|
|
636
555
|
|
|
556
|
+
|
|
557
|
+
updateStyles() {
|
|
558
|
+
if (this.styles)
|
|
559
|
+
for (let [style, oldText] of this.styles) {
|
|
560
|
+
let newText = style.textContent;
|
|
561
|
+
if (oldText !== newText)
|
|
562
|
+
Util.bindStyles(style, this.manager.rootEl);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
|
|
637
567
|
//#IFDEV
|
|
638
568
|
/**
|
|
639
569
|
* @deprecated
|
|
@@ -10,8 +10,7 @@ import {assert} from "../util/Errors.js";
|
|
|
10
10
|
* @typedef {Object} RenderOptions
|
|
11
11
|
* @property {boolean=} styles - Indicates whether the Courage component is present.
|
|
12
12
|
* @property {boolean=} scripts - Indicates whether the Power component is present.
|
|
13
|
-
* @property {boolean=} ids
|
|
14
|
-
*
|
|
13
|
+
* @property {boolean=} ids *
|
|
15
14
|
* @property {?boolean} render
|
|
16
15
|
* Used only when options are given to a class super constructor inheriting from Solarite.
|
|
17
16
|
* True to call render() immediately in super constructor.
|
|
@@ -66,6 +65,7 @@ export default class NodeGroupManager {
|
|
|
66
65
|
* @param rootEl {HTMLElement|DocumentFragment} If not specified, the first element of the html will be the rootEl. */
|
|
67
66
|
constructor(rootEl=null) {
|
|
68
67
|
this.rootEl = rootEl;
|
|
68
|
+
|
|
69
69
|
/*
|
|
70
70
|
//#IFDEV
|
|
71
71
|
|
|
@@ -107,126 +107,6 @@ export default class NodeGroupManager {
|
|
|
107
107
|
*/
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
/**
|
|
111
|
-
* Render the main template, which may indirectly call renderTemplate() to create children.
|
|
112
|
-
* @param template {Template}
|
|
113
|
-
* @param options {RenderOptions}
|
|
114
|
-
* @return {?DocumentFragment} */
|
|
115
|
-
render(template, options={}) {
|
|
116
|
-
this.mutationWatcherEnabled = false;
|
|
117
|
-
this.options = options;
|
|
118
|
-
this.clearSubscribers = false;
|
|
119
|
-
|
|
120
|
-
//#IFDEV
|
|
121
|
-
this.modifications = {
|
|
122
|
-
created: [],
|
|
123
|
-
updated: [],
|
|
124
|
-
moved: [],
|
|
125
|
-
deleted: []
|
|
126
|
-
};
|
|
127
|
-
//#ENDIF
|
|
128
|
-
|
|
129
|
-
if (!template && template !== '') {
|
|
130
|
-
this.rootEl.outerHTML = '';
|
|
131
|
-
this.mutationWatcherEnabled = true;
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Fast path for empty component.
|
|
136
|
-
if (template.html?.length === 1 && !template.html[0]) {
|
|
137
|
-
this.rootEl.innerHTML = '';
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
|
|
141
|
-
// Find or create a NodeGroup for the template.
|
|
142
|
-
// This updates all nodes from the template.
|
|
143
|
-
let close;
|
|
144
|
-
let exact = this.getNodeGroup(template, true);
|
|
145
|
-
if (!exact) {
|
|
146
|
-
close = this.getNodeGroup(template, false);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
let firstTime = !this.rootNg;
|
|
151
|
-
this.rootNg = exact || close;
|
|
152
|
-
|
|
153
|
-
// Reparent NodeGroup
|
|
154
|
-
// TODO: Move this to NodeGroup?
|
|
155
|
-
let parent = this.rootNg.getParentNode()
|
|
156
|
-
if (!this.rootEl)
|
|
157
|
-
this.rootEl = parent;
|
|
158
|
-
|
|
159
|
-
// If this is the first time rendering this element.
|
|
160
|
-
else if (firstTime) {
|
|
161
|
-
|
|
162
|
-
// Save slot children
|
|
163
|
-
let fragment;
|
|
164
|
-
if (this.rootEl.childNodes.length) {
|
|
165
|
-
fragment = document.createDocumentFragment();
|
|
166
|
-
fragment.append(...this.rootEl.childNodes);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Add rendered elements.
|
|
170
|
-
if (parent instanceof DocumentFragment)
|
|
171
|
-
this.rootEl.append(parent);
|
|
172
|
-
else if (parent)
|
|
173
|
-
this.rootEl.append(...parent.childNodes)
|
|
174
|
-
|
|
175
|
-
// Apply slot children
|
|
176
|
-
if (fragment) {
|
|
177
|
-
for (let slot of this.rootEl.querySelectorAll('slot[name]')) {
|
|
178
|
-
let name = slot.getAttribute('name')
|
|
179
|
-
if (name)
|
|
180
|
-
slot.append(...fragment.querySelectorAll(`[slot='${name}']`))
|
|
181
|
-
}
|
|
182
|
-
let unamedSlot = this.rootEl.querySelector('slot:not([name])')
|
|
183
|
-
if (unamedSlot)
|
|
184
|
-
unamedSlot.append(fragment)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// this.rootNg was rendered as childrenOnly=true
|
|
190
|
-
// Apply attributes from a root element to the real root element.
|
|
191
|
-
let ng = this.rootNg;
|
|
192
|
-
if (ng.pseudoRoot && ng.pseudoRoot !== this.rootEl) {
|
|
193
|
-
/*#IFDEV*/assert(this.rootEl)/*#ENDIF*/
|
|
194
|
-
|
|
195
|
-
// Remove old attributes
|
|
196
|
-
// for (let attrib of this.rootEl.attributes)
|
|
197
|
-
// if (attrib.name !== 'is' && attrib.name !== 'data-style' && !ng.pseudoRoot.hasAttribute(attrib.name))
|
|
198
|
-
// this.rootEl.removeAttribute(attrib.name)
|
|
199
|
-
|
|
200
|
-
// Add/set new attributes
|
|
201
|
-
if (firstTime)
|
|
202
|
-
for (let attrib of ng.pseudoRoot.attributes)
|
|
203
|
-
if (!this.rootEl.hasAttribute(attrib.name))
|
|
204
|
-
this.rootEl.setAttribute(attrib.name, attrib.value);
|
|
205
|
-
|
|
206
|
-
// ng.startNode = ng.endNode = this.rootEl;
|
|
207
|
-
// ng.nodesCache = [ng.startNode]
|
|
208
|
-
// for (let path of ng.paths) {
|
|
209
|
-
// if (path.nodeMarker === ng.rootEl)
|
|
210
|
-
// path.nodeMarker = this.rootEl;
|
|
211
|
-
// path.nodesCache = null;
|
|
212
|
-
// /*#IFDEV*/assert(path.nodeBefore !== ng.rootEl)/*#ENDIF*/
|
|
213
|
-
// }
|
|
214
|
-
//
|
|
215
|
-
// ng.rootEl = this.rootEl;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/*#IFDEV*/this.rootNg.verify();/*#ENDIF*/
|
|
219
|
-
this.reset();
|
|
220
|
-
/*#IFDEV*/this.rootNg.verify();/*#ENDIF*/
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
this.mutationWatcherEnabled = true;
|
|
224
|
-
return this.rootEl;
|
|
225
|
-
//#IFDEV
|
|
226
|
-
//return this.modifications;
|
|
227
|
-
//#ENDIF
|
|
228
|
-
}
|
|
229
|
-
|
|
230
110
|
|
|
231
111
|
/**
|
|
232
112
|
*
|
|
@@ -329,6 +209,8 @@ export default class NodeGroupManager {
|
|
|
329
209
|
// But it can still be a close match, so we don't use this code.
|
|
330
210
|
success = this.nodeGroupsAvailable.delete(ng2.closeKey, ng2);
|
|
331
211
|
/*#IFDEV*/assert(success);/*#ENDIF*/
|
|
212
|
+
|
|
213
|
+
this.nodeGroupsInUse.push(ng2);
|
|
332
214
|
}
|
|
333
215
|
}
|
|
334
216
|
}
|
|
@@ -363,7 +245,7 @@ export default class NodeGroupManager {
|
|
|
363
245
|
* but don't reparent it if it's somewhere else.
|
|
364
246
|
* @param template {Template}
|
|
365
247
|
* @param exact {?boolean}
|
|
366
|
-
* @param createForWatch
|
|
248
|
+
* @param createForWatch Deprecated.
|
|
367
249
|
* @return {?NodeGroup} */
|
|
368
250
|
getNodeGroup(template, exact=null, createForWatch=false) {
|
|
369
251
|
|
|
@@ -423,7 +305,7 @@ export default class NodeGroupManager {
|
|
|
423
305
|
// Perhaps also result could cache its last exprKey and then we'd use only one map?
|
|
424
306
|
ng.exactKey = exactKey;
|
|
425
307
|
ng.closeKey = closeKey;
|
|
426
|
-
if (createForWatch)
|
|
308
|
+
if (createForWatch)
|
|
427
309
|
this.nodeGroupsAvailable.add(ng.exactKey, ng);
|
|
428
310
|
else
|
|
429
311
|
this.nodeGroupsInUse.push(ng)
|
|
@@ -468,7 +350,10 @@ export default class NodeGroupManager {
|
|
|
468
350
|
//pathToLoopInfo = new MultiValueMap(); // uses a Set() for each value.
|
|
469
351
|
clearSubscribers = false;
|
|
470
352
|
|
|
353
|
+
//#IFDEV
|
|
354
|
+
|
|
471
355
|
/**
|
|
356
|
+
* @deprecated - part of watch.js (Watch v1)
|
|
472
357
|
* One path may be used to loop in more than one place, so we use this to get every anchor from each loop.
|
|
473
358
|
* @param path {Array}
|
|
474
359
|
* @return {LoopInfo[]} A function that gets the loop anchor NodeGroup */
|
|
@@ -477,6 +362,8 @@ export default class NodeGroupManager {
|
|
|
477
362
|
return [...this.pathToLoopInfo.getAll(serializedArrayPath)]; // This is set inside forEach()
|
|
478
363
|
}
|
|
479
364
|
|
|
365
|
+
//#ENDIF
|
|
366
|
+
|
|
480
367
|
|
|
481
368
|
/**
|
|
482
369
|
* @deprecated
|
|
@@ -503,7 +390,10 @@ export default class NodeGroupManager {
|
|
|
503
390
|
* Get the NodeGroupManager for a Web Component.
|
|
504
391
|
* @param rootEl {Solarite|HTMLElement}
|
|
505
392
|
* @return {NodeGroupManager} */
|
|
506
|
-
static get(rootEl) {
|
|
393
|
+
static get(rootEl=null) {
|
|
394
|
+
if (!rootEl)
|
|
395
|
+
return new NodeGroupManager();
|
|
396
|
+
|
|
507
397
|
let ngm = nodeGroupManagers.get(rootEl);
|
|
508
398
|
if (!ngm) {
|
|
509
399
|
ngm = new NodeGroupManager(rootEl);
|