solarite 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Solarite-debug.js +597 -420
- package/dist/Solarite.js +593 -373
- package/dist/Solarite.min.js +11 -2
- package/package.json +2 -2
- package/readme.md +2 -2
- package/src/ExprPath.js +110 -91
- package/src/Globals.js +9 -5
- package/src/NodeGroup.js +82 -245
- package/src/RootNodeGroup.js +153 -0
- package/src/Shell.js +12 -9
- package/src/Solarite.d.ts +8 -4
- package/src/Solarite.js +10 -8
- package/src/Template.js +135 -3
- package/src/Util.js +52 -14
- package/src/createSolarite.js +2 -2
- package/src/h.js +81 -129
- package/src/toEl.js +84 -0
- package/src/udomdiff.js +0 -55
- package/src/watch.js +2 -2
package/dist/Solarite-debug.js
CHANGED
|
@@ -105,8 +105,9 @@ function reset() {
|
|
|
105
105
|
Globals = {
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
109
|
-
|
|
108
|
+
* Dynamic values that should be passed to a Component's constructor and render() function.
|
|
109
|
+
* @type {Map<HTMLElement, any[]>} */
|
|
110
|
+
componentArgs: new Map(),
|
|
110
111
|
|
|
111
112
|
/**
|
|
112
113
|
* Store which instances of Solarite have already been added to the DOM.
|
|
@@ -121,6 +122,9 @@ function reset() {
|
|
|
121
122
|
|
|
122
123
|
div: document.createElement("div"),
|
|
123
124
|
|
|
125
|
+
/** @type {HTMLDocument} */
|
|
126
|
+
doc: document,
|
|
127
|
+
|
|
124
128
|
/**
|
|
125
129
|
* @type {Record<string, Class<Node>>} A map from built-in tag names to the constructors that create them. */
|
|
126
130
|
elementClasses: {},
|
|
@@ -139,20 +143,20 @@ function reset() {
|
|
|
139
143
|
nodeGroups: new WeakMap(),
|
|
140
144
|
|
|
141
145
|
/**
|
|
142
|
-
* Used by
|
|
146
|
+
* Used by h() path 9. */
|
|
143
147
|
objToEl: new WeakMap(),
|
|
144
148
|
|
|
145
149
|
//pendingChildren: [],
|
|
146
150
|
|
|
147
151
|
|
|
148
152
|
/**
|
|
149
|
-
* Elements that have been rendered to by
|
|
153
|
+
* Elements that have been rendered to by h() at least once.
|
|
150
154
|
* This is used by the Solarite class to know when to call onFirstConnect()
|
|
151
155
|
* @type {WeakSet<HTMLElement>} */
|
|
152
156
|
rendered: new WeakSet(),
|
|
153
157
|
|
|
154
158
|
/**
|
|
155
|
-
* Elements that are currently rendering via the
|
|
159
|
+
* Elements that are currently rendering via the h() function.
|
|
156
160
|
* @type {WeakSet<HTMLElement>} */
|
|
157
161
|
rendering: new WeakSet(),
|
|
158
162
|
|
|
@@ -239,12 +243,25 @@ let Util = {
|
|
|
239
243
|
return true; // the same.
|
|
240
244
|
},
|
|
241
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Convert HTMLElement attributes to an object.
|
|
248
|
+
* @param el {HTMLElement}
|
|
249
|
+
* @param ignore {?string} Optionally ignore this attribute.
|
|
250
|
+
* @return {Object} */
|
|
251
|
+
attribsToObject(el, ignore=null) {
|
|
252
|
+
let result = {};
|
|
253
|
+
for (let attrib of el.attributes)
|
|
254
|
+
if (attrib.name !== ignore)
|
|
255
|
+
result[Util.dashesToCamel(attrib.name)] = attrib.value;
|
|
256
|
+
return result;
|
|
257
|
+
},
|
|
258
|
+
|
|
242
259
|
bindId(root, el) {
|
|
243
260
|
let id = el.getAttribute('data-id') || el.getAttribute('id');
|
|
244
261
|
if (id) { // If something hasn't removed the id.
|
|
245
262
|
|
|
246
263
|
// Don't allow overwriting existing class properties if they already have a non-Node value.
|
|
247
|
-
if (root[id] && !(root[id]
|
|
264
|
+
if (root[id] && !(root[id]?.nodeType))
|
|
248
265
|
throw new Error(`${root.constructor.name}.${id} already has a value. ` +
|
|
249
266
|
`Can't set it as a reference to <${el.tagName.toLowerCase()} id="${id}">`);
|
|
250
267
|
|
|
@@ -253,26 +270,50 @@ let Util = {
|
|
|
253
270
|
},
|
|
254
271
|
|
|
255
272
|
/**
|
|
273
|
+
* If the style tab has a global attribute:
|
|
274
|
+
* 1. Put it in the document head as <style data-style="tag-name">...</style>
|
|
275
|
+
* 2. Replace the :host {...} CSS selector as tag-name {...}.
|
|
276
|
+
* Otherwise keep it where it is and:
|
|
277
|
+
* 1. Add data-style="1" attribute to the root element.
|
|
278
|
+
* 2. Replace the :host {...} selector in the style as tag-name[data-style='1'] {...}
|
|
256
279
|
* @param style {HTMLStyleElement}
|
|
257
280
|
* @param root {HTMLElement} */
|
|
258
281
|
bindStyles(style, root) {
|
|
259
|
-
let styleId = root.getAttribute('data-style');
|
|
260
|
-
if (!styleId) {
|
|
261
|
-
// Keep track of one style id for each class.
|
|
262
|
-
// TODO: Put this outside the class in a map, so it doesn't conflict with static properties.
|
|
263
|
-
if (!root.constructor.styleId)
|
|
264
|
-
root.constructor.styleId = 1;
|
|
265
|
-
styleId = root.constructor.styleId++;
|
|
266
282
|
|
|
267
|
-
|
|
283
|
+
let tagName = root.tagName.toLowerCase();
|
|
284
|
+
let styleId, attribSelector;
|
|
285
|
+
|
|
286
|
+
if (style.hasAttribute('global') || style.hasAttribute('data-global')) {
|
|
287
|
+
styleId = tagName;
|
|
288
|
+
attribSelector = '';
|
|
289
|
+
let doc = Globals$1.doc || root.ownerDocument || document;
|
|
290
|
+
if (!doc.head.querySelector(`style[data-style="${styleId}"]`)) {
|
|
291
|
+
doc.head.append(style);
|
|
292
|
+
style.setAttribute('data-style', styleId);
|
|
293
|
+
}
|
|
294
|
+
else // TODO: Make sure the style has no expressions.
|
|
295
|
+
style.remove(); // already in the head.
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
let styleId = root.getAttribute('data-style');
|
|
299
|
+
if (!styleId) {
|
|
300
|
+
// Keep track of one style id for each class.
|
|
301
|
+
// TODO: Put this outside the class in a map, so it doesn't conflict with static properties.
|
|
302
|
+
if (!root.constructor.styleId)
|
|
303
|
+
root.constructor.styleId = 1;
|
|
304
|
+
styleId = root.constructor.styleId++;
|
|
305
|
+
|
|
306
|
+
root.setAttribute('data-style', styleId);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
attribSelector = `[data-style="${styleId}"]`;
|
|
268
310
|
}
|
|
269
311
|
|
|
270
312
|
// Replace ":host" with "tagName[data-style=...]" in the css.
|
|
271
|
-
let tagName = root.tagName.toLowerCase();
|
|
272
313
|
for (let child of style.childNodes) {
|
|
273
314
|
if (child.nodeType === 3) {
|
|
274
315
|
let oldText = child.textContent;
|
|
275
|
-
let newText = oldText.replace(/:host(?=[^a-z0-9_])/gi, `${tagName}
|
|
316
|
+
let newText = oldText.replace(/:host(?=[^a-z0-9_])/gi, `${tagName}${attribSelector}`);
|
|
276
317
|
if (oldText !== newText)
|
|
277
318
|
child.textContent = newText;
|
|
278
319
|
}
|
|
@@ -319,7 +360,6 @@ let Util = {
|
|
|
319
360
|
return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
|
|
320
361
|
},
|
|
321
362
|
|
|
322
|
-
|
|
323
363
|
/**
|
|
324
364
|
* A generator function that recursively traverses and flattens a value.
|
|
325
365
|
*
|
|
@@ -360,7 +400,7 @@ let Util = {
|
|
|
360
400
|
|
|
361
401
|
/**
|
|
362
402
|
* Get the value of an input as the most appropriate JavaScript type.
|
|
363
|
-
* @param node {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|
|
|
403
|
+
* @param node {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLElement}
|
|
364
404
|
* @return {string|string[]|number|[]|File[]|Date|boolean} */
|
|
365
405
|
getInputValue(node) {
|
|
366
406
|
// .type is a built-in DOM property
|
|
@@ -374,6 +414,8 @@ let Util = {
|
|
|
374
414
|
return node.valueAsDate; // Date Object
|
|
375
415
|
if (node.type === 'select-multiple') // <select multiple>
|
|
376
416
|
return [...node.selectedOptions].map(option => option.value); // Array of Strings
|
|
417
|
+
if (node.hasAttribute('contenteditable'))
|
|
418
|
+
return node.innerHTML;
|
|
377
419
|
|
|
378
420
|
return node.value; // String
|
|
379
421
|
},
|
|
@@ -682,11 +724,6 @@ class MultiValueMap {
|
|
|
682
724
|
* @returns {Node[]} The same list of future children.
|
|
683
725
|
*/
|
|
684
726
|
const udomdiff = (parentNode, a, b, before) => {
|
|
685
|
-
//#IFDEV
|
|
686
|
-
// if (parentNode instanceof ExprPath)
|
|
687
|
-
// parentNode.verify();
|
|
688
|
-
//#ENDIF
|
|
689
|
-
|
|
690
727
|
const bLength = b.length;
|
|
691
728
|
let aEnd = a.length;
|
|
692
729
|
let bEnd = bLength;
|
|
@@ -708,13 +745,6 @@ const udomdiff = (parentNode, a, b, before) => {
|
|
|
708
745
|
while (bStart < bEnd) {
|
|
709
746
|
let bNode = b[bStart++];
|
|
710
747
|
parentNode.insertBefore(bNode, node);
|
|
711
|
-
|
|
712
|
-
//#IFDEV
|
|
713
|
-
if (bNode instanceof NodeGroup)
|
|
714
|
-
bNode.verify();
|
|
715
|
-
// if (parentNode instanceof ExprPath)
|
|
716
|
-
// parentNode.verify();
|
|
717
|
-
//#ENDIF
|
|
718
748
|
}
|
|
719
749
|
}
|
|
720
750
|
// remove head or tail: fast path
|
|
@@ -724,13 +754,6 @@ const udomdiff = (parentNode, a, b, before) => {
|
|
|
724
754
|
let aNode = a[aStart];
|
|
725
755
|
if (!map || !map.has(aNode)) {
|
|
726
756
|
parentNode.removeChild(aNode);
|
|
727
|
-
|
|
728
|
-
//#IFDEV
|
|
729
|
-
if (aNode instanceof NodeGroup)
|
|
730
|
-
aNode.verify();
|
|
731
|
-
// if (parentNode instanceof ExprPath)
|
|
732
|
-
// parentNode.verify();
|
|
733
|
-
//#ENDIF
|
|
734
757
|
}
|
|
735
758
|
aStart++;
|
|
736
759
|
}
|
|
@@ -767,24 +790,10 @@ const udomdiff = (parentNode, a, b, before) => {
|
|
|
767
790
|
a2,
|
|
768
791
|
b2.nextSibling
|
|
769
792
|
);
|
|
770
|
-
//#IFDEV
|
|
771
|
-
if (a2 instanceof NodeGroup)
|
|
772
|
-
a2.verify();
|
|
773
|
-
// if (parentNode instanceof ExprPath)
|
|
774
|
-
// parentNode.verify();
|
|
775
|
-
//#ENDIF
|
|
776
793
|
|
|
777
794
|
let bNode = b[--bEnd];
|
|
778
795
|
parentNode.insertBefore(bNode, node);
|
|
779
796
|
|
|
780
|
-
//#IFDEV
|
|
781
|
-
if (bNode instanceof NodeGroup)
|
|
782
|
-
bNode.verify();
|
|
783
|
-
// if (parentNode instanceof ExprPath)
|
|
784
|
-
// parentNode.verify();
|
|
785
|
-
|
|
786
|
-
//#ENDIF
|
|
787
|
-
|
|
788
797
|
// mark the future index as identical (yeah, it's dirty, but cheap 👍)
|
|
789
798
|
// The main reason to do this, is that when a[aEnd] will be reached,
|
|
790
799
|
// the loop will likely be on the fast path, as identical to b[bEnd].
|
|
@@ -832,14 +841,6 @@ const udomdiff = (parentNode, a, b, before) => {
|
|
|
832
841
|
while (bStart < index) {
|
|
833
842
|
let bNode = b[bStart++];
|
|
834
843
|
parentNode.insertBefore(bNode, node);
|
|
835
|
-
|
|
836
|
-
//#IFDEV
|
|
837
|
-
if (bNode instanceof NodeGroup)
|
|
838
|
-
bNode.verify();
|
|
839
|
-
// if (parentNode instanceof ExprPath)
|
|
840
|
-
// parentNode.verify();
|
|
841
|
-
|
|
842
|
-
//#ENDIF
|
|
843
844
|
}
|
|
844
845
|
}
|
|
845
846
|
// if the effort wasn't good enough, fallback to a replace,
|
|
@@ -852,13 +853,6 @@ const udomdiff = (parentNode, a, b, before) => {
|
|
|
852
853
|
bNode,
|
|
853
854
|
aNode
|
|
854
855
|
);
|
|
855
|
-
|
|
856
|
-
//#IFDEV
|
|
857
|
-
if (aNode instanceof NodeGroup)
|
|
858
|
-
aNode.verify();
|
|
859
|
-
// if (parentNode instanceof ExprPath)
|
|
860
|
-
// parentNode.verify();
|
|
861
|
-
//#ENDIF
|
|
862
856
|
}
|
|
863
857
|
}
|
|
864
858
|
// otherwise move the source forward, 'cause there's nothing to do
|
|
@@ -871,13 +865,6 @@ const udomdiff = (parentNode, a, b, before) => {
|
|
|
871
865
|
else {
|
|
872
866
|
let aNode = a[aStart++];
|
|
873
867
|
parentNode.removeChild(aNode);
|
|
874
|
-
|
|
875
|
-
//#IFDEV
|
|
876
|
-
if (aNode instanceof NodeGroup)
|
|
877
|
-
aNode.verify();
|
|
878
|
-
// if (parentNode instanceof ExprPath)
|
|
879
|
-
// parentNode.verify();
|
|
880
|
-
//#ENDIF
|
|
881
868
|
}
|
|
882
869
|
}
|
|
883
870
|
}
|
|
@@ -1005,13 +992,13 @@ class ExprPath {
|
|
|
1005
992
|
case 2: // PathType.Multiple:
|
|
1006
993
|
this.applyMultipleAttribs(this.nodeMarker, exprs[0]);
|
|
1007
994
|
break;
|
|
1008
|
-
case
|
|
995
|
+
case 4: // PathType.Comment:
|
|
1009
996
|
// Expressions inside Html comments. Deliberately empty because we won't waste time updating them.
|
|
1010
997
|
break;
|
|
1011
|
-
case
|
|
998
|
+
case 5: // PathType.Event:
|
|
1012
999
|
this.applyEventAttrib(this.nodeMarker, exprs[0], this.parentNg.rootNg.root);
|
|
1013
1000
|
break;
|
|
1014
|
-
default: //
|
|
1001
|
+
default: // 3 PathType.Attribute
|
|
1015
1002
|
// One attribute value may have multiple expressions. Here we apply them all at once.
|
|
1016
1003
|
this.applyValueAttrib(this.nodeMarker, exprs);
|
|
1017
1004
|
break;
|
|
@@ -1021,7 +1008,7 @@ class ExprPath {
|
|
|
1021
1008
|
/**
|
|
1022
1009
|
* Insert/replace the nodes created by a single expression.
|
|
1023
1010
|
* Called by applyExprs()
|
|
1024
|
-
* This function is recursive
|
|
1011
|
+
* This function is recursive. It calls functions that call applyNodes().
|
|
1025
1012
|
* @param expr {Expr}
|
|
1026
1013
|
* @param freeNodeGroups {boolean}
|
|
1027
1014
|
* @return {Node[]} New Nodes created. */
|
|
@@ -1056,7 +1043,6 @@ class ExprPath {
|
|
|
1056
1043
|
if (secondPass.length) {
|
|
1057
1044
|
for (let [nodesIndex, ngIndex] of secondPass) {
|
|
1058
1045
|
let ng = path.getNodeGroup(newNodes[nodesIndex], false);
|
|
1059
|
-
|
|
1060
1046
|
let ngNodes = ng.getNodes();
|
|
1061
1047
|
|
|
1062
1048
|
/*#IFDEV*/assert(!(newNodes[nodesIndex] instanceof NodeGroup));/*#ENDIF*/
|
|
@@ -1077,11 +1063,8 @@ class ExprPath {
|
|
|
1077
1063
|
|
|
1078
1064
|
/*#IFDEV*/assert(!path.nodeGroups.includes(null));/*#ENDIF*/
|
|
1079
1065
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
1066
|
let oldNodes = path.getNodes();
|
|
1083
1067
|
|
|
1084
|
-
|
|
1085
1068
|
// This pre-check makes it a few percent faster?
|
|
1086
1069
|
let same = Util.arraySame(oldNodes, newNodes);
|
|
1087
1070
|
if (!same) {
|
|
@@ -1094,7 +1077,6 @@ class ExprPath {
|
|
|
1094
1077
|
// Fast clear method
|
|
1095
1078
|
let isNowEmpty = oldNodes.length && !newNodes.length;
|
|
1096
1079
|
if (!isNowEmpty || !path.fastClear())
|
|
1097
|
-
|
|
1098
1080
|
// Rearrange nodes.
|
|
1099
1081
|
udomdiff(path.nodeMarker.parentNode, oldNodes, newNodes, path.nodeMarker);
|
|
1100
1082
|
|
|
@@ -1106,22 +1088,18 @@ class ExprPath {
|
|
|
1106
1088
|
if (!ng.startNode.parentNode)
|
|
1107
1089
|
ng.removeAndSaveOrphans();
|
|
1108
1090
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
1091
|
// Instantiate components created within ${...} expressions.
|
|
1113
|
-
//
|
|
1092
|
+
// Also see this.applyExactNodes() which handles calling render() on web components even if they are unchanged.
|
|
1114
1093
|
for (let el of newNodes) {
|
|
1115
|
-
if (el
|
|
1094
|
+
if (el?.nodeType === 1) { // HTMLElement
|
|
1116
1095
|
if (el.hasAttribute('solarite-placeholder'))
|
|
1117
|
-
this.parentNg.
|
|
1096
|
+
this.parentNg.handleComponent(el, null, true);
|
|
1118
1097
|
for (let child of el.querySelectorAll('[solarite-placeholder]'))
|
|
1119
|
-
this.parentNg.
|
|
1098
|
+
this.parentNg.handleComponent(child, null, true);
|
|
1120
1099
|
}
|
|
1121
1100
|
}
|
|
1122
1101
|
}
|
|
1123
1102
|
|
|
1124
|
-
|
|
1125
1103
|
/*#IFDEV*/path.verify();/*#ENDIF*/
|
|
1126
1104
|
}
|
|
1127
1105
|
|
|
@@ -1231,7 +1209,7 @@ class ExprPath {
|
|
|
1231
1209
|
}
|
|
1232
1210
|
|
|
1233
1211
|
// String/Number/Date/Boolean
|
|
1234
|
-
else if (!(expr instanceof Template) && !(expr
|
|
1212
|
+
else if (!(expr instanceof Template) && !(expr?.nodeType)){
|
|
1235
1213
|
// Convert expression to a string.
|
|
1236
1214
|
if (expr === undefined || expr === false || expr === null) // Util.isFalsy() inlined
|
|
1237
1215
|
expr = '';
|
|
@@ -1241,7 +1219,9 @@ class ExprPath {
|
|
|
1241
1219
|
// Get the same Template for the same string each time.
|
|
1242
1220
|
// let template = Globals.stringTemplates[expr];
|
|
1243
1221
|
// if (!template) {
|
|
1222
|
+
|
|
1244
1223
|
let template = new Template([expr], []);
|
|
1224
|
+
template.isText = true;
|
|
1245
1225
|
// Globals.stringTemplates[expr] = template;
|
|
1246
1226
|
//}
|
|
1247
1227
|
|
|
@@ -1266,12 +1246,44 @@ class ExprPath {
|
|
|
1266
1246
|
|
|
1267
1247
|
if (expr instanceof Template) {
|
|
1268
1248
|
let ng = this.getNodeGroup(expr, true);
|
|
1249
|
+
|
|
1269
1250
|
if (ng) {
|
|
1251
|
+
let newestNodes = ng.getNodes();
|
|
1252
|
+
newNodes.push(...newestNodes);
|
|
1253
|
+
|
|
1254
|
+
// New!
|
|
1255
|
+
// Re-apply all expressions if there's a web component, so we can pass them to its constructor.
|
|
1256
|
+
// NodeGroup.applyExprs() is used to call applyComponentExprs() on web components that have expression attributes.
|
|
1257
|
+
// For those that don't, we call applyComponentExprs() directly here.
|
|
1258
|
+
// Also see similar code at the end of this.applyNodes() which handles web components being instantiated the first time.
|
|
1259
|
+
let apply = false;
|
|
1260
|
+
for (let el of newestNodes) {
|
|
1261
|
+
if (el?.nodeType === 1) { // HTMLElement
|
|
1262
|
+
|
|
1263
|
+
if (el.tagName.includes('-')) {
|
|
1264
|
+
if (!expr.exprs.find(expr => expr?.nodeMarker === el))
|
|
1265
|
+
this.parentNg.handleComponent(el, null, true);
|
|
1266
|
+
else // Commenting out this "else" causes render() to be called too often, but other UI code fails if it's present.
|
|
1267
|
+
apply = true;
|
|
1268
|
+
}
|
|
1269
|
+
for (let child of el.querySelectorAll('*')) {
|
|
1270
|
+
if (child.tagName.includes('-')) {
|
|
1271
|
+
if (!expr.exprs.find(expr => expr?.nodeMarker === child))
|
|
1272
|
+
this.parentNg.handleComponent(child, null, true);
|
|
1273
|
+
else
|
|
1274
|
+
apply = true;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1270
1279
|
|
|
1271
|
-
//
|
|
1272
|
-
|
|
1273
|
-
|
|
1280
|
+
// This calls render() on web components that have expressions as attributes.
|
|
1281
|
+
if (apply)
|
|
1282
|
+
ng.applyExprs(expr.exprs);
|
|
1283
|
+
|
|
1274
1284
|
this.nodeGroups.push(ng);
|
|
1285
|
+
|
|
1286
|
+
return ng;
|
|
1275
1287
|
}
|
|
1276
1288
|
|
|
1277
1289
|
// If expression, mark it to be evaluated later in ExprPath.apply() to find partial match.
|
|
@@ -1283,10 +1295,10 @@ class ExprPath {
|
|
|
1283
1295
|
}
|
|
1284
1296
|
|
|
1285
1297
|
// Node(s) created by an expression.
|
|
1286
|
-
else if (expr
|
|
1298
|
+
else if (expr?.nodeType) {
|
|
1287
1299
|
|
|
1288
1300
|
// DocumentFragment created by an expression.
|
|
1289
|
-
if (expr
|
|
1301
|
+
if (expr?.nodeType === 11) // DocumentFragment
|
|
1290
1302
|
newNodes.push(...expr.childNodes);
|
|
1291
1303
|
else
|
|
1292
1304
|
newNodes.push(expr);
|
|
@@ -1318,16 +1330,30 @@ class ExprPath {
|
|
|
1318
1330
|
Globals$1.currentExprPath = null;
|
|
1319
1331
|
}
|
|
1320
1332
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1333
|
+
// Attribute as name: value object.
|
|
1334
|
+
if (typeof expr === 'object') {
|
|
1335
|
+
for (let name in expr) {
|
|
1336
|
+
let value = expr[name];
|
|
1337
|
+
if (value === undefined || value === false || value === null)
|
|
1338
|
+
continue;
|
|
1339
|
+
node.setAttribute(name, value);
|
|
1340
|
+
this.attrNames.add(name);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1325
1343
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1344
|
+
// Attributes as string
|
|
1345
|
+
else {
|
|
1346
|
+
let attrs = (expr + '') // Split string into multiple attributes.
|
|
1347
|
+
.split(/([\w-]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s]+))/g)
|
|
1348
|
+
.map(text => text.trim())
|
|
1349
|
+
.filter(text => text.length);
|
|
1350
|
+
|
|
1351
|
+
for (let attr of attrs) {
|
|
1352
|
+
let [name, value] = attr.split(/\s*=\s*/); // split on first equals.
|
|
1353
|
+
value = (value || '').replace(/^(['"])(.*)\1$/, '$2'); // trim value quotes if they match.
|
|
1354
|
+
node.setAttribute(name, value);
|
|
1355
|
+
this.attrNames.add(name);
|
|
1356
|
+
}
|
|
1331
1357
|
}
|
|
1332
1358
|
}
|
|
1333
1359
|
|
|
@@ -1348,8 +1374,8 @@ class ExprPath {
|
|
|
1348
1374
|
* @param root */
|
|
1349
1375
|
applyEventAttrib(node, expr, root) {
|
|
1350
1376
|
/*#IFDEV*/
|
|
1351
|
-
assert(this.type === ExprPathType.Event
|
|
1352
|
-
assert(root
|
|
1377
|
+
assert(this.type === ExprPathType.Event);
|
|
1378
|
+
assert(root?.nodeType === 1);
|
|
1353
1379
|
/*#ENDIF*/
|
|
1354
1380
|
|
|
1355
1381
|
let eventName = this.attrName.slice(2); // remove "on-" prefix.
|
|
@@ -1429,7 +1455,7 @@ class ExprPath {
|
|
|
1429
1455
|
}
|
|
1430
1456
|
|
|
1431
1457
|
/**
|
|
1432
|
-
*
|
|
1458
|
+
* Set the value of an attribute. This can be for any attribute, not just attributes named "value".
|
|
1433
1459
|
* @param node
|
|
1434
1460
|
* @param exprs */
|
|
1435
1461
|
// TODO: node is always this.nodeMarker?
|
|
@@ -1459,12 +1485,21 @@ class ExprPath {
|
|
|
1459
1485
|
}
|
|
1460
1486
|
else {
|
|
1461
1487
|
// TODO: should we remove isFalsy, since these are always props?
|
|
1462
|
-
|
|
1488
|
+
const strValue = Util.isFalsy(value) ? '' : value;
|
|
1489
|
+
|
|
1490
|
+
// Special case for contenteditable
|
|
1491
|
+
if (this.attrName === 'value' && node.hasAttribute('contenteditable')) {
|
|
1492
|
+
const existingValue = node.innerHTML;
|
|
1493
|
+
if (strValue !== existingValue)
|
|
1494
|
+
node.innerHTML = strValue;
|
|
1495
|
+
}
|
|
1496
|
+
else {
|
|
1463
1497
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1498
|
+
// If we don't have this condition, when we call render(), the browser will scroll to the currently
|
|
1499
|
+
// selected item in a <select> and mess up manually scrolling to a different value.
|
|
1500
|
+
if (strValue !== node[this.attrName])
|
|
1501
|
+
node[this.attrName] = strValue;
|
|
1502
|
+
}
|
|
1468
1503
|
}
|
|
1469
1504
|
|
|
1470
1505
|
// TODO: We need to remove any old listeners, like in bindEventAttribute.
|
|
@@ -1484,16 +1519,8 @@ class ExprPath {
|
|
|
1484
1519
|
|
|
1485
1520
|
// Regular attribute
|
|
1486
1521
|
else {
|
|
1487
|
-
//
|
|
1488
|
-
//
|
|
1489
|
-
//if (this.attrName === 'disabled')
|
|
1490
|
-
// debugger;
|
|
1491
|
-
|
|
1492
|
-
// hasOwnProperty() checks only the object, not the parents
|
|
1493
|
-
// this.attrName in node checks the node and the parents.
|
|
1494
|
-
// This version checks the html element it extends from, to see if has a setter set:
|
|
1495
|
-
// Object.getOwnPropertyDescriptor(Object.getPrototypeOf(node), this.attrName)?.set
|
|
1496
|
-
//let isProp = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(node), this.attrName)?.set;
|
|
1522
|
+
// Cache this on ExprPath.isHtmlProperty when Shell creates the props.
|
|
1523
|
+
// Have ExprPath.clone() copy .isHtmlProperty?
|
|
1497
1524
|
let isProp = this.isHtmlProperty;
|
|
1498
1525
|
if (isProp === undefined)
|
|
1499
1526
|
isProp = this.isHtmlProperty = Util.isHtmlProp(node, this.attrName);
|
|
@@ -1503,7 +1530,7 @@ class ExprPath {
|
|
|
1503
1530
|
if (!multiple) {
|
|
1504
1531
|
Globals$1.currentExprPath = this; // Used by watch()
|
|
1505
1532
|
if (typeof expr === 'function') {
|
|
1506
|
-
if (this.
|
|
1533
|
+
if (this.isComponent) { // Don't evaluate functions before passing them to components
|
|
1507
1534
|
return
|
|
1508
1535
|
}
|
|
1509
1536
|
this.watchFunction = expr; // The function that gets the expression, used for renderWatched()
|
|
@@ -1561,6 +1588,13 @@ class ExprPath {
|
|
|
1561
1588
|
// since we also prohibit expressions that are a child of textarea.
|
|
1562
1589
|
if (isProp)
|
|
1563
1590
|
node[this.attrName] = joinedValue;
|
|
1591
|
+
|
|
1592
|
+
// Allow one-way binding to contenteditable value attribute.
|
|
1593
|
+
// Contenteditables normally don't have a value attribute and have their content set via innerHTML.
|
|
1594
|
+
// Solarite doesn't allow contenteditables to have expressions as their children.
|
|
1595
|
+
else if (node.hasAttribute('contenteditable'))
|
|
1596
|
+
node.innerHTML = joinedValue;
|
|
1597
|
+
|
|
1564
1598
|
// TODO: Putting an 'else' here would be more performant
|
|
1565
1599
|
node.setAttribute(this.attrName, joinedValue);
|
|
1566
1600
|
}
|
|
@@ -1591,11 +1625,9 @@ class ExprPath {
|
|
|
1591
1625
|
nodeBefore = childNodes[this.nodeBeforeIndex];
|
|
1592
1626
|
|
|
1593
1627
|
let result = new ExprPath(nodeBefore, nodeMarker, this.type, this.attrName, this.attrValue);
|
|
1628
|
+
result.isComponent = this.isComponent;
|
|
1594
1629
|
|
|
1595
1630
|
//#IFDEV
|
|
1596
|
-
result.nodeMarker.exprPath = result;
|
|
1597
|
-
if (result.nodeBefore)
|
|
1598
|
-
result.nodeBefore.prevExprPath = result;
|
|
1599
1631
|
result.verify();
|
|
1600
1632
|
//#ENDIF
|
|
1601
1633
|
|
|
@@ -1604,9 +1636,7 @@ class ExprPath {
|
|
|
1604
1636
|
|
|
1605
1637
|
/**
|
|
1606
1638
|
* Clear the nodeCache of this ExprPath, as well as all parent and child ExprPaths that
|
|
1607
|
-
* share the same DOM parent node.
|
|
1608
|
-
*
|
|
1609
|
-
* TODO: Is recursive clearing ever necessary? */
|
|
1639
|
+
* share the same DOM parent node. */
|
|
1610
1640
|
clearNodesCache() {
|
|
1611
1641
|
let path = this;
|
|
1612
1642
|
|
|
@@ -1619,9 +1649,6 @@ class ExprPath {
|
|
|
1619
1649
|
// If stuck in an infinite loop here, the problem is likely due to Template hash colisions.
|
|
1620
1650
|
// Which cause one path to be the descendant of itself, creating a cycle.
|
|
1621
1651
|
}
|
|
1622
|
-
|
|
1623
|
-
// Commented out on Sep 30, 2024 b/c it was making the benchmark never finish when adding 10k rows.
|
|
1624
|
-
//clearChildNodeCache(this);
|
|
1625
1652
|
}
|
|
1626
1653
|
|
|
1627
1654
|
|
|
@@ -1663,7 +1690,7 @@ class ExprPath {
|
|
|
1663
1690
|
// result2.push(...ng.getNodes())
|
|
1664
1691
|
// return result2;
|
|
1665
1692
|
|
|
1666
|
-
if (this.type === ExprPathType.AttribValue || this.type === ExprPathType.AttribMultiple
|
|
1693
|
+
if (this.type === ExprPathType.AttribValue || this.type === ExprPathType.AttribMultiple) {
|
|
1667
1694
|
return [this.nodeMarker];
|
|
1668
1695
|
}
|
|
1669
1696
|
|
|
@@ -1723,11 +1750,13 @@ class ExprPath {
|
|
|
1723
1750
|
result = collection.deleteAny(template.getExactKey());
|
|
1724
1751
|
}
|
|
1725
1752
|
|
|
1726
|
-
if (result) // also delete the matching close key.
|
|
1753
|
+
if (result) {// also delete the matching close key.
|
|
1727
1754
|
collection.deleteSpecific(template.getCloseKey(), result);
|
|
1728
|
-
|
|
1729
|
-
|
|
1755
|
+
|
|
1756
|
+
//result.applyExprs(template.exprs);
|
|
1730
1757
|
}
|
|
1758
|
+
else
|
|
1759
|
+
return null;
|
|
1731
1760
|
}
|
|
1732
1761
|
|
|
1733
1762
|
// Find a close match.
|
|
@@ -1761,12 +1790,6 @@ class ExprPath {
|
|
|
1761
1790
|
return result;
|
|
1762
1791
|
}
|
|
1763
1792
|
|
|
1764
|
-
isComponent() {
|
|
1765
|
-
// Events won't have type===Component.
|
|
1766
|
-
// TODO: Have a special flag for components instead of it being on the type?
|
|
1767
|
-
return this.type === ExprPathType.ComponentAttribValue || (this.attrName && this.nodeMarker.tagName && this.nodeMarker.tagName.includes('-'));
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
1793
|
/**
|
|
1771
1794
|
* TODO: Rename this to nodeGroupsInUse, nodeGroupsAvialableAttached and nodeGroupsAvailableDetached?
|
|
1772
1795
|
* Nodes that have been used during the current render().
|
|
@@ -1824,7 +1847,7 @@ class ExprPath {
|
|
|
1824
1847
|
`parentNode: ${this.nodeBefore.parentNode?.tagName?.toLowerCase()}`,
|
|
1825
1848
|
'nodes:',
|
|
1826
1849
|
...setIndent(this.getNodes().map(item => {
|
|
1827
|
-
if (item
|
|
1850
|
+
if (item?.nodeType)
|
|
1828
1851
|
return item.outerHTML || item.textContent
|
|
1829
1852
|
else if (item instanceof NodeGroup)
|
|
1830
1853
|
return item.debug
|
|
@@ -1881,7 +1904,7 @@ class ExprPath {
|
|
|
1881
1904
|
/** @enum {int} */
|
|
1882
1905
|
const ExprPathType = {
|
|
1883
1906
|
/** Child of a node */
|
|
1884
|
-
Content: 1,
|
|
1907
|
+
Content: 1, // TODO: Rename to Nodes
|
|
1885
1908
|
|
|
1886
1909
|
/** One or more whole attributes */
|
|
1887
1910
|
AttribMultiple: 2,
|
|
@@ -1889,14 +1912,11 @@ const ExprPathType = {
|
|
|
1889
1912
|
/** Value of an attribute. */
|
|
1890
1913
|
AttribValue: 3,
|
|
1891
1914
|
|
|
1892
|
-
/** Value of an attribute being passed to a component. */
|
|
1893
|
-
ComponentAttribValue: 4,
|
|
1894
|
-
|
|
1895
1915
|
/** Expressions inside Html comments. */
|
|
1896
|
-
Comment:
|
|
1916
|
+
Comment: 4,
|
|
1897
1917
|
|
|
1898
1918
|
/** Value of an attribute. */
|
|
1899
|
-
Event:
|
|
1919
|
+
Event: 5,
|
|
1900
1920
|
};
|
|
1901
1921
|
|
|
1902
1922
|
|
|
@@ -2064,7 +2084,7 @@ class Shell {
|
|
|
2064
2084
|
//#ENDIF
|
|
2065
2085
|
|
|
2066
2086
|
if (html.length === 1 && !html[0].match(/[<&]/)) {
|
|
2067
|
-
this.fragment =
|
|
2087
|
+
this.fragment = Globals$1.doc.createTextNode(html[0]);
|
|
2068
2088
|
return;
|
|
2069
2089
|
}
|
|
2070
2090
|
|
|
@@ -2072,18 +2092,18 @@ class Shell {
|
|
|
2072
2092
|
// 1. Add placeholders
|
|
2073
2093
|
let joinedHtml = Shell.addPlaceholders(html);
|
|
2074
2094
|
|
|
2075
|
-
let template =
|
|
2095
|
+
let template = Globals$1.doc.createElement('template'); // Using a single global template won't keep the nodes as children of the DocumentFragment.
|
|
2076
2096
|
if (joinedHtml)
|
|
2077
2097
|
template.innerHTML = joinedHtml;
|
|
2078
2098
|
else // Create one text node, so shell isn't empty and NodeGroups created from it have something to point the startNode and endNode at.
|
|
2079
|
-
template.content.append(
|
|
2099
|
+
template.content.append(Globals$1.doc.createTextNode(''));
|
|
2080
2100
|
this.fragment = template.content;
|
|
2081
2101
|
|
|
2082
2102
|
// 2. Find placeholders
|
|
2083
2103
|
let node;
|
|
2084
2104
|
let toRemove = [];
|
|
2085
2105
|
let placeholdersUsed = 0;
|
|
2086
|
-
const walker =
|
|
2106
|
+
const walker = Globals$1.doc.createTreeWalker(this.fragment, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT);
|
|
2087
2107
|
while (node = walker.nextNode()) {
|
|
2088
2108
|
|
|
2089
2109
|
// Remove previous after each iteration, so paths will still be calculated correctly.
|
|
@@ -2119,10 +2139,13 @@ class Shell {
|
|
|
2119
2139
|
// Replace comment placeholders
|
|
2120
2140
|
else if (node.nodeType === 8 && node.nodeValue === '!✨!') {
|
|
2121
2141
|
|
|
2142
|
+
if (node?.parentNode?.closest && node?.parentNode?.closest('[contenteditable]'))
|
|
2143
|
+
throw new Error(`Contenteditable can't have expressions inside them. Use <div contenteditable value="\${...}"> instead.`);
|
|
2144
|
+
|
|
2122
2145
|
// Get or create nodeBefore.
|
|
2123
2146
|
let nodeBefore = node.previousSibling; // Can be the same as another Path's nodeMarker.
|
|
2124
2147
|
if (!nodeBefore) {
|
|
2125
|
-
nodeBefore =
|
|
2148
|
+
nodeBefore = Globals$1.doc.createComment('ExprPath:'+this.paths.length);
|
|
2126
2149
|
node.parentNode.insertBefore(nodeBefore, node);
|
|
2127
2150
|
}
|
|
2128
2151
|
/*#IFDEV*/assert(nodeBefore);/*#ENDIF*/
|
|
@@ -2147,9 +2170,9 @@ class Shell {
|
|
|
2147
2170
|
placeholdersUsed ++;
|
|
2148
2171
|
}
|
|
2149
2172
|
|
|
2173
|
+
// Comments become text nodes when inside textareas.
|
|
2150
2174
|
else if (node.nodeType === 3 && node.parentNode?.tagName === 'TEXTAREA' && node.textContent.includes('<!--!✨!-->'))
|
|
2151
2175
|
throw new Error(`Textarea can't have expressions inside them. Use <textarea value="\${...}"> instead.`);
|
|
2152
|
-
|
|
2153
2176
|
|
|
2154
2177
|
|
|
2155
2178
|
// Sometimes users will comment out a block of html code that has expressions.
|
|
@@ -2173,7 +2196,7 @@ class Shell {
|
|
|
2173
2196
|
|
|
2174
2197
|
let placeholders = [];
|
|
2175
2198
|
for (let i = 0; i<parts.length; i++) {
|
|
2176
|
-
let current =
|
|
2199
|
+
let current = Globals$1.doc.createTextNode(parts[i]);
|
|
2177
2200
|
node.parentNode.insertBefore(current, node);
|
|
2178
2201
|
if (i > 0)
|
|
2179
2202
|
placeholders.push(current);
|
|
@@ -2212,9 +2235,9 @@ class Shell {
|
|
|
2212
2235
|
path.nodeMarkerPath = getNodePath(path.nodeMarker);
|
|
2213
2236
|
|
|
2214
2237
|
// Cache so we don't have to calculate this later inside NodeGroup.applyExprs()
|
|
2215
|
-
if (path.type === ExprPathType.AttribValue && path.nodeMarker.nodeType === 1 &&
|
|
2238
|
+
if ((path.type === ExprPathType.AttribValue || path.type === ExprPathType.Event) && path.nodeMarker.nodeType === 1 &&
|
|
2216
2239
|
(path.nodeMarker.tagName.includes('-') || path.nodeMarker.hasAttribute('is'))) {
|
|
2217
|
-
path.
|
|
2240
|
+
path.isComponent = true;
|
|
2218
2241
|
}
|
|
2219
2242
|
}
|
|
2220
2243
|
|
|
@@ -2388,30 +2411,39 @@ class NodeGroup {
|
|
|
2388
2411
|
* @type {?Map<HTMLStyleElement, string>} */
|
|
2389
2412
|
styles;
|
|
2390
2413
|
|
|
2414
|
+
dynamicComponents = new Set();
|
|
2415
|
+
staticComponents = [];
|
|
2416
|
+
|
|
2417
|
+
/** @type {Template} */
|
|
2418
|
+
template;
|
|
2419
|
+
|
|
2391
2420
|
|
|
2392
2421
|
/**
|
|
2393
2422
|
* Create an "instantiated" NodeGroup from a Template and add it to an element.
|
|
2394
2423
|
* @param template {Template} Create it from the html strings and expressions in this template.
|
|
2395
2424
|
* @param parentPath {?ExprPath} */
|
|
2396
2425
|
constructor(template, parentPath=null) {
|
|
2426
|
+
this.rootNg = parentPath?.parentNg?.rootNg || this;
|
|
2427
|
+
this.parentPath = parentPath;
|
|
2428
|
+
|
|
2397
2429
|
if (!(this instanceof RootNodeGroup)) {
|
|
2398
2430
|
|
|
2399
|
-
let [fragment, shell] = this.
|
|
2431
|
+
let [fragment, shell] = this.populateFromTemplate(template);
|
|
2400
2432
|
|
|
2401
2433
|
if (fragment && template.exprs.length) {
|
|
2402
2434
|
this.updatePaths(fragment, shell.paths);
|
|
2403
2435
|
|
|
2404
2436
|
// Static web components can sometimes have children created via expressions.
|
|
2405
2437
|
// But calling applyExprs() will mess up the shell's path to them.
|
|
2406
|
-
// So we find them first, then call
|
|
2407
|
-
|
|
2438
|
+
// So we find them first, then call instantiateStaticComponents() after their children have been created.
|
|
2439
|
+
this.staticComponents = this.findStaticComponents(fragment, shell);
|
|
2408
2440
|
|
|
2409
2441
|
this.activateEmbeds(fragment, shell);
|
|
2410
2442
|
|
|
2411
2443
|
// Apply exprs
|
|
2412
2444
|
this.applyExprs(template.exprs);
|
|
2413
2445
|
|
|
2414
|
-
this.instantiateStaticComponents(staticComponents);
|
|
2446
|
+
this.instantiateStaticComponents(this.staticComponents);
|
|
2415
2447
|
}
|
|
2416
2448
|
else if (shell)
|
|
2417
2449
|
this.activateEmbeds(fragment, shell);
|
|
@@ -2422,47 +2454,33 @@ class NodeGroup {
|
|
|
2422
2454
|
* Common init shared by RootNodeGroup and NodeGroup constructors.
|
|
2423
2455
|
* But in a separate function because they need to do this at a different step.
|
|
2424
2456
|
* @param template {Template} Create it from the html strings and expressions in this template.
|
|
2425
|
-
* @
|
|
2426
|
-
|
|
2427
|
-
* @param closeKey {?string}
|
|
2428
|
-
* @returns {[DocumentFragment, Shell]} */
|
|
2429
|
-
init(template, parentPath=null, exactKey=null, closeKey=null) {
|
|
2430
|
-
this.exactKey = exactKey || template.getExactKey();
|
|
2431
|
-
this.closeKey = closeKey || template.getCloseKey();
|
|
2432
|
-
|
|
2433
|
-
this.parentPath = parentPath;
|
|
2434
|
-
this.rootNg = parentPath?.parentNg?.rootNg || this;
|
|
2435
|
-
|
|
2457
|
+
* @returns {[DocumentFragment, Shell]} The Shell created from the template,a nd the fragment cloned from the Shell.*/
|
|
2458
|
+
populateFromTemplate(template) {
|
|
2436
2459
|
/*#IFDEV*/assert(this.rootNg);/*#ENDIF*/
|
|
2437
|
-
|
|
2438
|
-
/** @type {Template} */
|
|
2439
2460
|
this.template = template;
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
template.nodeGroup = this;
|
|
2443
|
-
|
|
2444
|
-
// Get a cached version of the parsed and instantiated html, and ExprPaths.
|
|
2461
|
+
this.exactKey = template.getExactKey();
|
|
2462
|
+
this.closeKey = template.getCloseKey();
|
|
2445
2463
|
|
|
2446
2464
|
// If it's just a text node, skip a bunch of unnecessary steps.
|
|
2447
|
-
if (
|
|
2448
|
-
|
|
2449
|
-
let textNode = document.createTextNode(template.html[0]);
|
|
2450
|
-
|
|
2465
|
+
if (template.isText) {
|
|
2466
|
+
let textNode = Globals$1.doc.createTextNode(template.html[0]);
|
|
2451
2467
|
this.startNode = this.endNode = textNode;
|
|
2452
2468
|
return [];
|
|
2453
2469
|
}
|
|
2470
|
+
|
|
2471
|
+
// Get a cached version of the parsed and instantiated html, and ExprPaths:
|
|
2454
2472
|
else {
|
|
2455
2473
|
let shell = Shell.get(template.html);
|
|
2456
2474
|
let fragment = shell.fragment.cloneNode(true);
|
|
2457
2475
|
|
|
2458
|
-
if (fragment
|
|
2476
|
+
if (fragment?.nodeType === 11) { // DocumentFragment
|
|
2459
2477
|
let childNodes = fragment.childNodes;
|
|
2460
2478
|
this.startNode = childNodes[0];
|
|
2461
2479
|
this.endNode = childNodes[childNodes.length - 1];
|
|
2462
2480
|
}
|
|
2463
|
-
else
|
|
2481
|
+
else
|
|
2464
2482
|
this.startNode = this.endNode = fragment;
|
|
2465
|
-
|
|
2483
|
+
|
|
2466
2484
|
return [fragment, shell];
|
|
2467
2485
|
}
|
|
2468
2486
|
}
|
|
@@ -2501,6 +2519,7 @@ class NodeGroup {
|
|
|
2501
2519
|
exprIndex--;
|
|
2502
2520
|
}
|
|
2503
2521
|
|
|
2522
|
+
|
|
2504
2523
|
// TODO: Need to end and restart this block when going from one component to the next?
|
|
2505
2524
|
// Think of having two adjacent components.
|
|
2506
2525
|
// But the dynamicAttribsAdjacet test already passes.
|
|
@@ -2509,11 +2528,11 @@ class NodeGroup {
|
|
|
2509
2528
|
// 1. Instantiate it if it hasn't already been, sending all expr's to its constructor.
|
|
2510
2529
|
// 2. Otherwise send them to its render function.
|
|
2511
2530
|
// Components with no expressions as attributes are instead activated in activateEmbeds().
|
|
2512
|
-
if (path.nodeMarker !== this.rootNg.root && path.isComponent
|
|
2531
|
+
if (path.nodeMarker !== this.rootNg.root && path.isComponent) {
|
|
2513
2532
|
|
|
2514
|
-
if (!nextPath || !nextPath.isComponent
|
|
2533
|
+
if (!nextPath || !nextPath.isComponent || nextPath.nodeMarker !== path.nodeMarker)
|
|
2515
2534
|
lastComponentPathIndex = i;
|
|
2516
|
-
let isFirstComponentPath = !prevPath || !prevPath.isComponent
|
|
2535
|
+
let isFirstComponentPath = !prevPath || !prevPath.isComponent || prevPath.nodeMarker !== path.nodeMarker;
|
|
2517
2536
|
|
|
2518
2537
|
if (isFirstComponentPath) {
|
|
2519
2538
|
|
|
@@ -2523,7 +2542,7 @@ class NodeGroup {
|
|
|
2523
2542
|
componentProps[attrName] = pathExprs[j].length > 1 ? pathExprs[j].join('') : pathExprs[j][0];
|
|
2524
2543
|
}
|
|
2525
2544
|
|
|
2526
|
-
this.
|
|
2545
|
+
this.handleComponent(path.nodeMarker, componentProps, true);
|
|
2527
2546
|
|
|
2528
2547
|
// Set attributes on component.
|
|
2529
2548
|
for (let j=i; j<=lastComponentPathIndex; j++)
|
|
@@ -2542,7 +2561,10 @@ class NodeGroup {
|
|
|
2542
2561
|
// TODO: Only do this if we have ExprPaths within styles?
|
|
2543
2562
|
this.updateStyles();
|
|
2544
2563
|
|
|
2545
|
-
|
|
2564
|
+
// Call render() on static web components. This makes the component.staticAttribs() test work.
|
|
2565
|
+
for (let el of this.staticComponents)
|
|
2566
|
+
if (el.render)
|
|
2567
|
+
el.render(Util.attribsToObject(el)); // It has no expressions.
|
|
2546
2568
|
|
|
2547
2569
|
// Invalidate the nodes cache because we just changed it.
|
|
2548
2570
|
this.nodesCache = null;
|
|
@@ -2558,50 +2580,39 @@ class NodeGroup {
|
|
|
2558
2580
|
}
|
|
2559
2581
|
|
|
2560
2582
|
/**
|
|
2561
|
-
*
|
|
2562
|
-
* @param el {
|
|
2563
|
-
* @param props {Object}
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
// Perhaps if Components were treated as child NodeGroups, which would need to be the child of an ExprPath,
|
|
2568
|
-
// then we could re-use the hash and logic from NodeManager?
|
|
2569
|
-
let newHash = getObjectHash(props);
|
|
2570
|
-
|
|
2583
|
+
* Unified path to ensure a child component is instantiated (if placeholder) and optionally rendered.
|
|
2584
|
+
* @param el {HTMLElement}
|
|
2585
|
+
* @param props {?Object}
|
|
2586
|
+
* @param doRender {boolean}
|
|
2587
|
+
* @return {HTMLElement} The (possibly replaced) element. */
|
|
2588
|
+
handleComponent(el, props=null, doRender=true) {
|
|
2571
2589
|
let isPreHtmlElement = el.hasAttribute('solarite-placeholder');
|
|
2572
2590
|
let isPreIsElement = el.hasAttribute('_is');
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
// Instantiate a placeholder.
|
|
2591
|
+
let attribs, children;
|
|
2576
2592
|
if (isPreHtmlElement || isPreIsElement)
|
|
2577
|
-
el = this.instantiateComponent(el, isPreHtmlElement, props);
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
// compare the arguments and then decide for itself whether it wants to re-render.
|
|
2582
|
-
else if (el.render) {
|
|
2583
|
-
//let oldHash = Globals.componentArgsHash.get(el);
|
|
2584
|
-
//if (oldHash !== newHash) { // Only if not changed.
|
|
2585
|
-
let args = {};
|
|
2593
|
+
[el, attribs, children] = this.instantiateComponent(el, isPreHtmlElement, props);
|
|
2594
|
+
if (doRender && el.render) {
|
|
2595
|
+
if (!attribs) {
|
|
2596
|
+
attribs = Util.attribsToObject(el);
|
|
2586
2597
|
for (let name in props || {})
|
|
2587
|
-
|
|
2588
|
-
el.
|
|
2589
|
-
|
|
2598
|
+
attribs[Util.dashesToCamel(name)] = props[name];
|
|
2599
|
+
children = el.childNodes;
|
|
2600
|
+
}
|
|
2601
|
+
el.render(attribs, children);
|
|
2590
2602
|
}
|
|
2591
|
-
|
|
2592
|
-
Globals$1.componentArgsHash.set(el, newHash);
|
|
2603
|
+
return el;
|
|
2593
2604
|
}
|
|
2594
|
-
|
|
2605
|
+
|
|
2595
2606
|
/**
|
|
2596
2607
|
* We swap the placeholder element for the real element so we can pass its dynamic attributes
|
|
2597
2608
|
* to its constructor.
|
|
2609
|
+
* This is only called by handleComponent()
|
|
2610
|
+
* This does not call render()
|
|
2598
2611
|
*
|
|
2599
|
-
*
|
|
2600
|
-
*
|
|
2601
|
-
* @param el
|
|
2612
|
+
* @param el {HTMLElement}
|
|
2602
2613
|
* @param isPreHtmlElement {?boolean} True if the element's tag name ends with -solarite-placeholder
|
|
2603
2614
|
* @param props {Object} Attributes with dynamic values.
|
|
2604
|
-
* @return {HTMLElement} */
|
|
2615
|
+
* @return {[HTMLElement, attribs:Object, children:Node[]]}} */
|
|
2605
2616
|
instantiateComponent(el, isPreHtmlElement=undefined, props=undefined) {
|
|
2606
2617
|
if (isPreHtmlElement === undefined)
|
|
2607
2618
|
isPreHtmlElement = !el.hasAttribute('_is');
|
|
@@ -2616,24 +2627,17 @@ class NodeGroup {
|
|
|
2616
2627
|
if (!Constructor)
|
|
2617
2628
|
throw new Error(`The custom tag name ${tagName} is not registered.`)
|
|
2618
2629
|
|
|
2619
|
-
let args = {};
|
|
2620
|
-
for (let name in props || {})
|
|
2621
|
-
args[Util.dashesToCamel(name)] = props[name];
|
|
2622
|
-
|
|
2623
2630
|
// Pass other attribs to constructor, since otherwise they're not yet set on the element,
|
|
2624
2631
|
// and the constructor would otherwise have no way to see them.
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
args[attribName] = attrib.value;
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
+
let attribs = Util.attribsToObject(el, 'solarite-placeholder');
|
|
2633
|
+
for (let name in props || {})
|
|
2634
|
+
attribs[Util.dashesToCamel(name)] = props[name];
|
|
2635
|
+
|
|
2632
2636
|
|
|
2633
2637
|
// Create the web component.
|
|
2634
2638
|
// Get the children that aren't Solarite's comment placeholders.
|
|
2635
|
-
let
|
|
2636
|
-
let newEl = new Constructor(
|
|
2639
|
+
let children = [...el.childNodes].filter(node => node.nodeType !== Node.COMMENT_NODE || !node.nodeValue.startsWith('ExprPath'));
|
|
2640
|
+
let newEl = new Constructor(attribs, children);
|
|
2637
2641
|
|
|
2638
2642
|
if (!isPreHtmlElement)
|
|
2639
2643
|
newEl.setAttribute('is', el.getAttribute('is').toLowerCase());
|
|
@@ -2659,7 +2663,7 @@ class NodeGroup {
|
|
|
2659
2663
|
if (this.endNode === el)
|
|
2660
2664
|
this.endNode = newEl;
|
|
2661
2665
|
|
|
2662
|
-
|
|
2666
|
+
// This is used only if inheriting from the Solarite class.
|
|
2663
2667
|
// applyComponentExprs() is called because we're rendering.
|
|
2664
2668
|
// So we want to render the sub-component also.
|
|
2665
2669
|
if (newEl.renderFirstTime)
|
|
@@ -2683,7 +2687,7 @@ class NodeGroup {
|
|
|
2683
2687
|
newEl.setAttribute(name, val);
|
|
2684
2688
|
}
|
|
2685
2689
|
|
|
2686
|
-
return newEl;
|
|
2690
|
+
return [newEl, attribs, children];
|
|
2687
2691
|
}
|
|
2688
2692
|
|
|
2689
2693
|
/**
|
|
@@ -2730,18 +2734,21 @@ class NodeGroup {
|
|
|
2730
2734
|
* Requires the nodeCache to be present. */
|
|
2731
2735
|
removeAndSaveOrphans() {
|
|
2732
2736
|
/*#IFDEV*/assert(this.nodesCache);/*#ENDIF*/
|
|
2733
|
-
let fragment =
|
|
2737
|
+
let fragment = Globals$1.doc.createDocumentFragment();
|
|
2734
2738
|
for (let node of this.getNodes())
|
|
2735
2739
|
fragment.append(node);
|
|
2736
2740
|
}
|
|
2737
2741
|
|
|
2738
2742
|
|
|
2739
|
-
|
|
2740
|
-
|
|
2743
|
+
/**
|
|
2744
|
+
* @param fragment {DocumentFragment}
|
|
2745
|
+
* @param paths
|
|
2746
|
+
* @param startingPathDepth {int} */
|
|
2747
|
+
updatePaths(fragment, paths, startingPathDepth) {
|
|
2741
2748
|
let pathLength = paths.length;
|
|
2742
2749
|
this.paths.length = pathLength;
|
|
2743
2750
|
for (let i=0; i<pathLength; i++) {
|
|
2744
|
-
let path = paths[i].clone(fragment,
|
|
2751
|
+
let path = paths[i].clone(fragment, startingPathDepth);
|
|
2745
2752
|
path.parentNg = this;
|
|
2746
2753
|
this.paths[i] = path;
|
|
2747
2754
|
}
|
|
@@ -2768,7 +2775,7 @@ class NodeGroup {
|
|
|
2768
2775
|
`parentNode: ${this.parentNode?.tagName?.toLowerCase()}`,
|
|
2769
2776
|
'nodes:',
|
|
2770
2777
|
...setIndent(this.getNodes().map(item => {
|
|
2771
|
-
if (item
|
|
2778
|
+
if (item?.nodeType) {
|
|
2772
2779
|
|
|
2773
2780
|
let tree = nodeToArrayTree(item, nextNode => {
|
|
2774
2781
|
|
|
@@ -2823,7 +2830,7 @@ class NodeGroup {
|
|
|
2823
2830
|
}
|
|
2824
2831
|
//#ENDIF
|
|
2825
2832
|
|
|
2826
|
-
findStaticComponents(root, shell,
|
|
2833
|
+
findStaticComponents(root, shell, startingPathDepth=0) {
|
|
2827
2834
|
let result = [];
|
|
2828
2835
|
|
|
2829
2836
|
// static components. These are WebComponents that do not have any constructor arguments that are expressions.
|
|
@@ -2831,8 +2838,8 @@ class NodeGroup {
|
|
|
2831
2838
|
// Maybe someday these two paths will be merged?
|
|
2832
2839
|
// Must happen before ids because instantiateComponent will replace the element.
|
|
2833
2840
|
for (let path of shell.staticComponents) {
|
|
2834
|
-
if (
|
|
2835
|
-
path = path.slice(0, -
|
|
2841
|
+
if (startingPathDepth)
|
|
2842
|
+
path = path.slice(0, -startingPathDepth);
|
|
2836
2843
|
let el = resolveNodePath(root, path);
|
|
2837
2844
|
|
|
2838
2845
|
// Shell doesn't know if a web component is the pseudoRoot so we have to detect it here.
|
|
@@ -2844,8 +2851,9 @@ class NodeGroup {
|
|
|
2844
2851
|
}
|
|
2845
2852
|
|
|
2846
2853
|
instantiateStaticComponents(staticComponents) {
|
|
2847
|
-
|
|
2848
|
-
|
|
2854
|
+
// TODO: Why do we not call render() on the static component here? The tests pass either way.
|
|
2855
|
+
for (let i in staticComponents)
|
|
2856
|
+
staticComponents[i] = this.handleComponent(staticComponents[i], null, false);
|
|
2849
2857
|
}
|
|
2850
2858
|
|
|
2851
2859
|
/**
|
|
@@ -2866,7 +2874,7 @@ class NodeGroup {
|
|
|
2866
2874
|
let el = resolveNodePath(root, path);
|
|
2867
2875
|
Util.bindId(rootEl, el);
|
|
2868
2876
|
}
|
|
2869
|
-
|
|
2877
|
+
}
|
|
2870
2878
|
|
|
2871
2879
|
// styles
|
|
2872
2880
|
if (options?.styles !== false) {
|
|
@@ -2896,9 +2904,8 @@ class NodeGroup {
|
|
|
2896
2904
|
}
|
|
2897
2905
|
}
|
|
2898
2906
|
}
|
|
2899
|
-
}
|
|
2900
|
-
|
|
2901
|
-
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2902
2909
|
class RootNodeGroup extends NodeGroup {
|
|
2903
2910
|
|
|
2904
2911
|
/**
|
|
@@ -2912,18 +2919,18 @@ class RootNodeGroup extends NodeGroup {
|
|
|
2912
2919
|
exprsToRender = new Map();
|
|
2913
2920
|
|
|
2914
2921
|
/**
|
|
2915
|
-
*
|
|
2916
|
-
* @param
|
|
2917
|
-
* @param
|
|
2918
|
-
* @param options {?object}
|
|
2919
|
-
*/
|
|
2922
|
+
* @param template {Template}
|
|
2923
|
+
* @param el {?HTMLElement} Optional, pre-existing htmlElement tat will be the root.
|
|
2924
|
+
* @param options {?object} */
|
|
2920
2925
|
constructor(template, el, options) {
|
|
2921
2926
|
super(template);
|
|
2922
2927
|
|
|
2923
2928
|
this.options = options;
|
|
2924
2929
|
|
|
2925
|
-
|
|
2926
|
-
|
|
2930
|
+
let [fragment, shell] = this.populateFromTemplate(template);
|
|
2931
|
+
|
|
2932
|
+
let startingPathDepth = 0;
|
|
2933
|
+
|
|
2927
2934
|
|
|
2928
2935
|
if (fragment instanceof Text) {
|
|
2929
2936
|
|
|
@@ -2934,25 +2941,25 @@ class RootNodeGroup extends NodeGroup {
|
|
|
2934
2941
|
el.append(fragment);
|
|
2935
2942
|
this.root = el;
|
|
2936
2943
|
}
|
|
2944
|
+
else
|
|
2945
|
+
throw new Error('Cannot create a standalone text node');
|
|
2937
2946
|
Globals$1.nodeGroups.set(this.root, this);
|
|
2938
2947
|
}
|
|
2948
|
+
|
|
2949
|
+
|
|
2939
2950
|
else {
|
|
2940
2951
|
|
|
2941
2952
|
// If adding NodeGroup to an element.
|
|
2942
|
-
let offset = 0;
|
|
2943
|
-
let root = fragment; // TODO: Rename so it's not confused with this.root.
|
|
2944
2953
|
if (el) {
|
|
2945
|
-
|
|
2954
|
+
this.root = el;
|
|
2946
2955
|
|
|
2947
2956
|
// Save slot children
|
|
2948
2957
|
let slotChildren;
|
|
2949
2958
|
if (el.childNodes.length) {
|
|
2950
|
-
slotChildren =
|
|
2959
|
+
slotChildren = Globals$1.doc.createDocumentFragment();
|
|
2951
2960
|
slotChildren.append(...el.childNodes);
|
|
2952
2961
|
}
|
|
2953
2962
|
|
|
2954
|
-
this.root = el;
|
|
2955
|
-
|
|
2956
2963
|
// If el should replace the root node of the fragment.
|
|
2957
2964
|
if (isReplaceEl(fragment, el)) {
|
|
2958
2965
|
el.append(...fragment.children[0].childNodes);
|
|
@@ -2963,8 +2970,10 @@ class RootNodeGroup extends NodeGroup {
|
|
|
2963
2970
|
el.setAttribute(attrib.name, attrib.value);
|
|
2964
2971
|
|
|
2965
2972
|
// Go one level deeper into all of shell's paths.
|
|
2966
|
-
|
|
2967
|
-
}
|
|
2973
|
+
startingPathDepth = 1;
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
else {
|
|
2968
2977
|
let isEmpty = fragment.childNodes.length === 1 && fragment.childNodes[0].nodeType === 3 && fragment.childNodes[0].textContent === '';
|
|
2969
2978
|
if (!isEmpty)
|
|
2970
2979
|
el.append(...fragment.childNodes);
|
|
@@ -2992,35 +3001,35 @@ class RootNodeGroup extends NodeGroup {
|
|
|
2992
3001
|
el.append(slotChildren);
|
|
2993
3002
|
}
|
|
2994
3003
|
|
|
2995
|
-
root = el;
|
|
2996
|
-
|
|
2997
3004
|
this.startNode = el;
|
|
2998
3005
|
this.endNode = el;
|
|
2999
|
-
}
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
// Instantiate as a standalone element.
|
|
3009
|
+
else {
|
|
3000
3010
|
let singleEl = getSingleEl(fragment);
|
|
3001
|
-
this.root = singleEl || fragment; // We return the whole fragment when calling
|
|
3011
|
+
this.root = singleEl || fragment; // We return the whole fragment when calling h() with a collection of nodes.
|
|
3002
3012
|
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
root = singleEl;
|
|
3006
|
-
offset = 1;
|
|
3007
|
-
}
|
|
3013
|
+
if (singleEl)
|
|
3014
|
+
startingPathDepth = 1;
|
|
3008
3015
|
}
|
|
3009
|
-
|
|
3010
|
-
this.updatePaths(root, shell.paths,
|
|
3016
|
+
Globals$1.nodeGroups.set(this.root, this);
|
|
3017
|
+
this.updatePaths(this.root, shell.paths, startingPathDepth);
|
|
3011
3018
|
|
|
3012
3019
|
// Static web components can sometimes have children created via expressions.
|
|
3013
3020
|
// But calling applyExprs() will mess up the shell's path to them.
|
|
3014
3021
|
// So we find them first, then call activateStaticComponents() after their children have been created.
|
|
3015
|
-
|
|
3022
|
+
this.staticComponents = this.findStaticComponents(this.root, shell, startingPathDepth);
|
|
3016
3023
|
|
|
3017
|
-
this.activateEmbeds(root, shell,
|
|
3024
|
+
this.activateEmbeds(this.root, shell, startingPathDepth);
|
|
3018
3025
|
|
|
3019
3026
|
// Apply exprs
|
|
3020
3027
|
this.applyExprs(template.exprs);
|
|
3021
3028
|
|
|
3022
|
-
this.instantiateStaticComponents(staticComponents);
|
|
3029
|
+
this.instantiateStaticComponents(this.staticComponents);
|
|
3023
3030
|
}
|
|
3031
|
+
|
|
3032
|
+
|
|
3024
3033
|
}
|
|
3025
3034
|
}
|
|
3026
3035
|
|
|
@@ -3062,8 +3071,7 @@ class Template {
|
|
|
3062
3071
|
/** @type {Array} Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
|
|
3063
3072
|
hashedFields;
|
|
3064
3073
|
|
|
3065
|
-
|
|
3066
|
-
nodeGroup;
|
|
3074
|
+
isText;
|
|
3067
3075
|
|
|
3068
3076
|
/**
|
|
3069
3077
|
*
|
|
@@ -3168,169 +3176,203 @@ class Template {
|
|
|
3168
3176
|
|
|
3169
3177
|
return this.closeKey;
|
|
3170
3178
|
}
|
|
3171
|
-
}
|
|
3172
3179
|
|
|
3180
|
+
/**
|
|
3181
|
+
* @param tag {string}
|
|
3182
|
+
* @param props {?Record<string, any>}
|
|
3183
|
+
* @param children
|
|
3184
|
+
* @returns {Template} */
|
|
3185
|
+
static fromJsx(tag, props, children) {
|
|
3173
3186
|
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
* @property {boolean=} styles - Replace :host in style tags to scope them locally.
|
|
3177
|
-
* @property {boolean=} scripts - Execute script tags.
|
|
3178
|
-
* @property {boolean=} ids - Create references to elements with id or data-id attributes.
|
|
3179
|
-
* @property {?boolean} render - Deprecated.
|
|
3180
|
-
* Used only when options are given to a class super constructor inheriting from Solarite.
|
|
3181
|
-
* True to call render() immediately in super constructor.
|
|
3182
|
-
* False to automatically call render() at all.
|
|
3183
|
-
* Undefined (default) to call render() when added to the DOM, unless already rendered.
|
|
3184
|
-
*/
|
|
3185
|
-
|
|
3186
|
-
/**
|
|
3187
|
-
* Convert strings to HTMLNodes.
|
|
3188
|
-
* Using h`...` as a tag will always create a Template.
|
|
3189
|
-
* Using h() as a function() will always create a DOM element.
|
|
3190
|
-
*
|
|
3191
|
-
* Features beyond what standard js tagged template strings do:
|
|
3192
|
-
* 1. r`` sub-expressions
|
|
3193
|
-
* 2. functions, nodes, and arrays of nodes as sub-expressions.
|
|
3194
|
-
* 3. html-escape all expressions by default, unless wrapped in r()
|
|
3195
|
-
* 4. event binding
|
|
3196
|
-
* 5. TODO: list more
|
|
3197
|
-
*
|
|
3198
|
-
* Currently supported:
|
|
3199
|
-
* 1. h(el, options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
|
|
3200
|
-
* 2. h(el, template, ?options) // Render the Template created by #1 to element.
|
|
3201
|
-
*
|
|
3202
|
-
* 3. h`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
|
|
3203
|
-
*
|
|
3204
|
-
* 4. h('Hello'); // Create single text node.
|
|
3205
|
-
* 5. h('<b>Hello</b>'); // Create single HTMLElement
|
|
3206
|
-
* 6. h('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
|
|
3207
|
-
* 7. h()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which
|
|
3208
|
-
* // includes properly handling nested components and r`` sub-expressions.
|
|
3209
|
-
* 8. h(template) // Render Template created by #1.
|
|
3210
|
-
*
|
|
3211
|
-
* 9. h({render(){...}}) // Pass an object with a render method, and optionally other props/methods.
|
|
3212
|
-
* 10. h(string, object, ...) // JSX TODO
|
|
3213
|
-
* @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
|
|
3214
|
-
* @param exprs {*[]|string|Template|Object}
|
|
3215
|
-
* @return {Node|HTMLElement|Template} */
|
|
3216
|
-
function h(htmlStrings=undefined, ...exprs) {
|
|
3187
|
+
// HTML void elements that must not have closing tags
|
|
3188
|
+
const isVoid = selfClosingTags.has(tag.toLowerCase());
|
|
3217
3189
|
|
|
3218
|
-
|
|
3219
|
-
|
|
3190
|
+
// Build htmlStrings/exprs so Shell can place placeholders in attribute values and child content.
|
|
3191
|
+
let htmlStrings = [];
|
|
3192
|
+
let templateExprs = [];
|
|
3220
3193
|
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
let parent = htmlStrings, template = exprs[0];
|
|
3194
|
+
// Opening tag
|
|
3195
|
+
let open = `<${tag}`;
|
|
3224
3196
|
|
|
3225
|
-
//
|
|
3226
|
-
if (
|
|
3227
|
-
|
|
3228
|
-
|
|
3197
|
+
// Attributes
|
|
3198
|
+
if (props && typeof props === 'object') {
|
|
3199
|
+
for (let name in props) {
|
|
3200
|
+
let value = props[name];
|
|
3229
3201
|
|
|
3230
|
-
|
|
3202
|
+
// id and data-id are static in templates — never expressions
|
|
3203
|
+
if (name === 'id' || name === 'data-id') {
|
|
3204
|
+
// Write directly into the opening string with quotes
|
|
3205
|
+
open += ` ${name}="${value}"`;
|
|
3206
|
+
continue;
|
|
3207
|
+
}
|
|
3231
3208
|
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3209
|
+
// Dynamic attribute value: functions are unquoted (e.g., onclick=${fn}), others quoted
|
|
3210
|
+
if (typeof value === 'function') {
|
|
3211
|
+
open += ` ${name}=`;
|
|
3212
|
+
htmlStrings.push(open);
|
|
3213
|
+
templateExprs.push(value);
|
|
3214
|
+
// reset so subsequent attributes start fresh (e.g., ' title=')
|
|
3215
|
+
open = ``;
|
|
3216
|
+
}
|
|
3217
|
+
else {
|
|
3218
|
+
open += ` ${name}=`;
|
|
3219
|
+
htmlStrings.push(open);
|
|
3220
|
+
templateExprs.push(value);
|
|
3221
|
+
// reset so subsequent attributes start fresh (e.g., ' title=')
|
|
3222
|
+
open = ``;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3239
3225
|
}
|
|
3240
3226
|
|
|
3241
|
-
//
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
//
|
|
3249
|
-
|
|
3250
|
-
|
|
3227
|
+
// Finalize opening tag precisely to match tagged template splitting
|
|
3228
|
+
if (!isVoid) {
|
|
3229
|
+
const pushedAny = htmlStrings.length > 0;
|
|
3230
|
+
// If nothing pushed yet (no dynamic attrs), push the entire open + '>'
|
|
3231
|
+
if (!pushedAny)
|
|
3232
|
+
htmlStrings.push(open + '>');
|
|
3233
|
+
else {
|
|
3234
|
+
// If we were in a quoted attr (open === '"'), then the string after expr is '">' ;
|
|
3235
|
+
// Otherwise (function-valued attr), the string after expr is just '>'
|
|
3236
|
+
htmlStrings.push(open === '"' ? '">' : '>');
|
|
3251
3237
|
}
|
|
3238
|
+
|
|
3239
|
+
for (let child of children)
|
|
3240
|
+
addChild(child, htmlStrings, templateExprs);
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
// Closing tag (not for void tags)
|
|
3244
|
+
if (!isVoid) {
|
|
3245
|
+
// If we never emitted the '>' for the open tag (no children were added),
|
|
3246
|
+
// then it was appended above before children. Now just add the closing tag to the last html segment.
|
|
3247
|
+
let lastIdx = htmlStrings.length - 1;
|
|
3248
|
+
htmlStrings[lastIdx] += `</${tag}>`;
|
|
3249
|
+
}
|
|
3250
|
+
else {
|
|
3251
|
+
// Void element: ensure we emitted a trailing '>' segment
|
|
3252
|
+
const pushedAny = htmlStrings.length > 0;
|
|
3253
|
+
if (!pushedAny)
|
|
3254
|
+
htmlStrings.push(open + '>');
|
|
3255
|
+
else
|
|
3256
|
+
htmlStrings.push('>');
|
|
3252
3257
|
}
|
|
3253
|
-
}
|
|
3254
3258
|
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3259
|
+
// Ensure invariant
|
|
3260
|
+
//assert(htmlStrings.length === templateExprs.length + 1);
|
|
3261
|
+
//console.log([htmlStrings, templateExprs])
|
|
3262
|
+
return new Template(htmlStrings, templateExprs);
|
|
3258
3263
|
}
|
|
3264
|
+
}
|
|
3259
3265
|
|
|
3260
|
-
else if (typeof htmlStrings === 'string' || htmlStrings instanceof String) {
|
|
3261
|
-
// 10. JSX
|
|
3262
|
-
if (typeof exprs[0] === 'object') {
|
|
3263
|
-
exprs[0] || {};
|
|
3264
|
-
exprs.slice(1);
|
|
3265
3266
|
|
|
3266
|
-
|
|
3267
|
-
let templateExprs = [];
|
|
3267
|
+
const selfClosingTags = new Set(['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr']);
|
|
3268
3268
|
|
|
3269
|
-
// TODO How to know which children are static html and which are expression placeholders?
|
|
3270
|
-
// Perhaps we have to treat every text child as a string?
|
|
3271
3269
|
|
|
3272
|
-
|
|
3273
|
-
|
|
3270
|
+
/**
|
|
3271
|
+
* Add child Templates that were already created via h() and Template.fromJsx()
|
|
3272
|
+
* @param template {Template}
|
|
3273
|
+
* @param html {string[]}
|
|
3274
|
+
* @param exprs {any[]} */
|
|
3275
|
+
const addChild = (template, html, exprs) => {
|
|
3276
|
+
|
|
3277
|
+
if (Array.isArray(template)) {
|
|
3278
|
+
for (let c of template)
|
|
3279
|
+
addChild(c, html, exprs);
|
|
3280
|
+
}
|
|
3281
|
+
else {
|
|
3282
|
+
let flatten = false;
|
|
3283
|
+
if (template instanceof Template) {
|
|
3284
|
+
// Heuristic to match tagged-template splitting:
|
|
3285
|
+
// - Flatten if the child has expressions (so JSX can inline attribute/value placeholders like tagged literals would).
|
|
3286
|
+
// - Also flatten void elements (e.g., <img>) so they inline like literals.
|
|
3287
|
+
// - Otherwise, keep as a dynamic child placeholder to match cases where the tagged template used an expression child.
|
|
3288
|
+
const childHasExprs = template.exprs.length > 0;
|
|
3289
|
+
if (childHasExprs)
|
|
3290
|
+
flatten = true;
|
|
3291
|
+
else {
|
|
3292
|
+
const m = (template.html[0] || '').match(/^<([a-zA-Z][\w:-]*)/);
|
|
3293
|
+
const childTag = m ? m[1].toLowerCase() : '';
|
|
3294
|
+
flatten = selfClosingTags.has(childTag);
|
|
3295
|
+
}
|
|
3274
3296
|
}
|
|
3275
3297
|
|
|
3298
|
+
if (flatten) {
|
|
3299
|
+
// Flatten/interleave into current segment to match tagged template splitting
|
|
3300
|
+
html[html.length - 1] += template.html[0];
|
|
3301
|
+
for (let i = 0; i < template.exprs.length; i++) {
|
|
3302
|
+
exprs.push(template.exprs[i]);
|
|
3303
|
+
html.push(template.html[i + 1] ?? '');
|
|
3304
|
+
}
|
|
3305
|
+
} else {
|
|
3306
|
+
// Keep as dynamic child
|
|
3307
|
+
exprs.push(template);
|
|
3308
|
+
html.push('');
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
};
|
|
3276
3312
|
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3313
|
+
|
|
3314
|
+
/**
|
|
3315
|
+
* @typedef {Object} RenderOptions
|
|
3316
|
+
* @property {boolean=} styles - Replace :host in style tags to scope them locally.
|
|
3317
|
+
* @property {boolean=} scripts - Execute script tags.
|
|
3318
|
+
* @property {boolean=} ids - Create references to elements with id or data-id attributes.
|
|
3319
|
+
* @property {?boolean} render - Deprecated.
|
|
3320
|
+
* Used only when options are given to a class super constructor inheriting from Solarite.
|
|
3321
|
+
* True to call render() immediately in super constructor.
|
|
3322
|
+
* False to automatically call render() at all.
|
|
3323
|
+
* Undefined (default) to call render() when added to the DOM, unless already rendered.
|
|
3324
|
+
*/
|
|
3325
|
+
|
|
3326
|
+
/**
|
|
3327
|
+
* Convert a template, string, or object into a DOM Node or Element
|
|
3328
|
+
*
|
|
3329
|
+
* 1. h('Hello'); // Create single text node.
|
|
3330
|
+
* 2. h('<b>Hello</b>'); // Create single HTMLElement
|
|
3331
|
+
* 3. h('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
|
|
3332
|
+
* 4. h(template) // Render Template created by h`<html>` or h();
|
|
3333
|
+
* 5. h({render(){...}}) // Pass an object with a render method, and optionally other props/methods.
|
|
3334
|
+
* @param arg {string|Template|{render:()=>void}}
|
|
3335
|
+
* @returns {Node|DocumentFragment|HTMLElement} */
|
|
3336
|
+
function toEl(arg) {
|
|
3337
|
+
|
|
3338
|
+
if (typeof arg === 'string') {
|
|
3339
|
+
let html = arg;
|
|
3340
|
+
|
|
3341
|
+
// If it's an element with whitespace before or after it, trim both ends.
|
|
3342
|
+
if (html.match(/^\s^</) || html.match(/>\s+$/))
|
|
3343
|
+
html = html.trim();
|
|
3281
3344
|
|
|
3282
3345
|
// We create a new one each time because otherwise
|
|
3283
3346
|
// the returned fragment will have its content replaced by a subsequent call.
|
|
3284
|
-
let templateEl =
|
|
3285
|
-
templateEl.innerHTML =
|
|
3347
|
+
let templateEl = Globals$1.doc.createElement('template');
|
|
3348
|
+
templateEl.innerHTML = html;
|
|
3286
3349
|
|
|
3287
|
-
//
|
|
3350
|
+
// 1+2. Return Node if there's one child.
|
|
3288
3351
|
let relevantNodes = Util.trimEmptyNodes(templateEl.content.childNodes);
|
|
3289
3352
|
if (relevantNodes.length === 1)
|
|
3290
3353
|
return relevantNodes[0];
|
|
3291
3354
|
|
|
3292
|
-
//
|
|
3355
|
+
// 3. Otherwise return DocumentFragment.
|
|
3293
3356
|
return templateEl.content;
|
|
3294
3357
|
}
|
|
3295
3358
|
|
|
3296
|
-
//
|
|
3297
|
-
|
|
3298
|
-
return (
|
|
3299
|
-
//Globals.rendered.add(parent)
|
|
3300
|
-
let template = h(htmlStrings, ...exprs);
|
|
3301
|
-
return template.render();
|
|
3302
|
-
}
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
|
-
// 8.
|
|
3306
|
-
else if (htmlStrings instanceof Template) {
|
|
3307
|
-
return htmlStrings.render();
|
|
3359
|
+
// 4.
|
|
3360
|
+
if (arg instanceof Template) {
|
|
3361
|
+
return arg.render();
|
|
3308
3362
|
}
|
|
3309
3363
|
|
|
3310
|
-
|
|
3311
|
-
// 9. Create dynamic element with render() function.
|
|
3364
|
+
// 5. Create dynamic element from an object with a render() function.
|
|
3312
3365
|
// TODO: This path doesn't handle embeds like data-id="..."
|
|
3313
|
-
else if (typeof
|
|
3314
|
-
let obj =
|
|
3366
|
+
else if (arg && typeof arg === 'object') {
|
|
3367
|
+
let obj = arg;
|
|
3315
3368
|
|
|
3316
|
-
if (obj.constructor.name !== 'Object')
|
|
3369
|
+
if (obj.constructor.name !== 'Object')
|
|
3317
3370
|
throw new Error(`Solarate Web Component class ${obj.constructor?.name} must extend HTMLElement.`);
|
|
3318
3371
|
|
|
3319
|
-
|
|
3320
|
-
// Special rebound render path, called by normal path.
|
|
3321
|
-
// Intercepts the main r`...` function call inside render().
|
|
3322
|
-
if (Globals$1.objToEl.has(obj)) {
|
|
3323
|
-
return function(...args) {
|
|
3324
|
-
let template = h(...args);
|
|
3325
|
-
let el = template.render();
|
|
3326
|
-
Globals$1.objToEl.set(obj, el);
|
|
3327
|
-
}.bind(obj);
|
|
3328
|
-
}
|
|
3329
|
-
|
|
3330
3372
|
// Normal path
|
|
3331
|
-
|
|
3373
|
+
if (!Globals$1.objToEl.has(obj)) {
|
|
3332
3374
|
Globals$1.objToEl.set(obj, null);
|
|
3333
|
-
obj[renderF](); // Calls the Special rebound render path above, when the render function calls
|
|
3375
|
+
obj[renderF](); // Calls the Special rebound render path above, when the render function calls h(this)
|
|
3334
3376
|
let el = Globals$1.objToEl.get(obj);
|
|
3335
3377
|
Globals$1.objToEl.delete(obj);
|
|
3336
3378
|
|
|
@@ -3338,7 +3380,7 @@ function h(htmlStrings=undefined, ...exprs) {
|
|
|
3338
3380
|
if (typeof obj[name] === 'function')
|
|
3339
3381
|
el[name] = obj[name].bind(el); // Make the "this" of functions be el.
|
|
3340
3382
|
// TODO: But this doesn't work for passing an object with functions as a constructor arg via an attribute:
|
|
3341
|
-
|
|
3383
|
+
// <my-element arg=${{myFunc() { return this }}}
|
|
3342
3384
|
else
|
|
3343
3385
|
el[name] = obj[name];
|
|
3344
3386
|
|
|
@@ -3354,13 +3396,145 @@ function h(htmlStrings=undefined, ...exprs) {
|
|
|
3354
3396
|
}
|
|
3355
3397
|
}
|
|
3356
3398
|
|
|
3357
|
-
|
|
3358
|
-
|
|
3399
|
+
throw new Error('toEl() does not support argument of type: ' + (arg ? typeof arg : arg));
|
|
3400
|
+
|
|
3359
3401
|
}
|
|
3360
3402
|
|
|
3403
|
+
|
|
3361
3404
|
// Trick to prevent minifier from renaming this function.
|
|
3362
3405
|
let renderF = 'render';
|
|
3363
3406
|
|
|
3407
|
+
/**
|
|
3408
|
+
* Convert strings to HTMLNodes.
|
|
3409
|
+
* Using h`...` as a tag will always create a Template.
|
|
3410
|
+
* Using h() as a function() will always create a DOM element.
|
|
3411
|
+
*
|
|
3412
|
+
* Features beyond what standard js tagged template strings do:
|
|
3413
|
+
* 1. r`` sub-expressions
|
|
3414
|
+
* 2. functions, nodes, and arrays of nodes as sub-expressions.
|
|
3415
|
+
* 3. html-escape all expressions by default, unless wrapped in h()
|
|
3416
|
+
* 4. event binding
|
|
3417
|
+
* 5. TODO: list more
|
|
3418
|
+
*
|
|
3419
|
+
* General rule:
|
|
3420
|
+
* If h() is a function with null or an HTMLElement as its first argument create a Node.
|
|
3421
|
+
* Otherwise create a template
|
|
3422
|
+
*
|
|
3423
|
+
* Currently supported:
|
|
3424
|
+
*
|
|
3425
|
+
* Create Tempataes
|
|
3426
|
+
* 1. h`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
|
|
3427
|
+
* 2. h('<b>Hello</b><u>Goodbye</u>'); // Create Template from string, that can later be used to create nodes.
|
|
3428
|
+
*
|
|
3429
|
+
* Add children to an element.
|
|
3430
|
+
* 3. h(el, h`<b>${'Hi'}</b>`, ?options)
|
|
3431
|
+
* 4. h(el, ?options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
|
|
3432
|
+
*
|
|
3433
|
+
* Create top-level element
|
|
3434
|
+
* 5. h()`Hello<b>${'World'}!</b>`
|
|
3435
|
+
*
|
|
3436
|
+
* 6. h(string, object, ...) // Used for JSX
|
|
3437
|
+
* @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
|
|
3438
|
+
* @param exprs {*[]|string|Template|Object}
|
|
3439
|
+
* @return {Node|HTMLElement|Template} */
|
|
3440
|
+
function h(htmlStrings=undefined, ...exprs) {
|
|
3441
|
+
|
|
3442
|
+
// 1. Tagged template
|
|
3443
|
+
if (Array.isArray(arguments[0])) {
|
|
3444
|
+
return new Template(arguments[0], exprs);
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
// 2. String to template, or JSX factory form h(tag, props, ...children)
|
|
3448
|
+
else if (typeof arguments[0] === 'string' || arguments[0] instanceof String) {
|
|
3449
|
+
let tagOrHtml = arguments[0];
|
|
3450
|
+
|
|
3451
|
+
// 2a. JSX: h("tag", {props}, ...children)
|
|
3452
|
+
if (exprs.length && (typeof exprs[0] === 'object' || exprs[0] === null)) {
|
|
3453
|
+
let tag = tagOrHtml + '';
|
|
3454
|
+
let props = exprs[0] || {};
|
|
3455
|
+
let children = exprs.slice(1);
|
|
3456
|
+
|
|
3457
|
+
return Template.fromJsx(tag, props, children);
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
// 2b. Plain html string => template
|
|
3461
|
+
else {
|
|
3462
|
+
let html = tagOrHtml;
|
|
3463
|
+
// If it starts with whitespace, trim both ends.
|
|
3464
|
+
// TODO: Also trim if it ends with whitespace?
|
|
3465
|
+
if (html.match(/^\s^</))
|
|
3466
|
+
html = html.trim();
|
|
3467
|
+
return new Template([html], []);
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
else if (arguments[0] instanceof HTMLElement || arguments[0] instanceof DocumentFragment) {
|
|
3472
|
+
|
|
3473
|
+
// 3. Render template to element.
|
|
3474
|
+
if (arguments[1] instanceof Template) {
|
|
3475
|
+
|
|
3476
|
+
/** @type Template */
|
|
3477
|
+
let template = arguments[1];
|
|
3478
|
+
let parent = arguments[0];
|
|
3479
|
+
let options = arguments[2]; // deprecated?
|
|
3480
|
+
template.render(parent, options);
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
// 4. Render tagged template to element
|
|
3484
|
+
else {
|
|
3485
|
+
let parent = arguments[0], options = arguments[1];
|
|
3486
|
+
|
|
3487
|
+
// Remove shadowroot. TODO: This could mess up paths?
|
|
3488
|
+
if (parent.shadowRoot)
|
|
3489
|
+
parent.innerHTML = '';
|
|
3490
|
+
|
|
3491
|
+
// Return a tagged template function that applies the tagged template to parent.
|
|
3492
|
+
let taggedTemplate = (htmlStrings, ...exprs) => {
|
|
3493
|
+
Globals$1.rendered.add(parent);
|
|
3494
|
+
let template = new Template(htmlStrings, exprs);
|
|
3495
|
+
return template.render(parent, options);
|
|
3496
|
+
};
|
|
3497
|
+
return taggedTemplate;
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
// 5. Create a static element h()'<div></div>' (Deprecated?)
|
|
3502
|
+
else if (!arguments.length) {
|
|
3503
|
+
return (htmlStrings, ...exprs) => {
|
|
3504
|
+
let template = h(htmlStrings, ...exprs);
|
|
3505
|
+
return toEl(template); // Go to path 6.
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
// 6. Help toEl() with objects.
|
|
3510
|
+
// Special rebound render path, called by normal path.
|
|
3511
|
+
// Intercepts the main h(this)`...` function call inside render().
|
|
3512
|
+
// TODO: This path doesn't handle embeds like data-id="..."
|
|
3513
|
+
else if (typeof arguments[0] === 'object' && Globals$1.objToEl.has(arguments[0])) {
|
|
3514
|
+
let obj = arguments[0];
|
|
3515
|
+
|
|
3516
|
+
if (obj.constructor.name !== 'Object')
|
|
3517
|
+
throw new Error(`Solarate Web Component class ${obj.constructor?.name} must extend HTMLElement.`);
|
|
3518
|
+
|
|
3519
|
+
// Jsx with h(this, <jsx>)
|
|
3520
|
+
if (arguments[1] instanceof Template) {
|
|
3521
|
+
let template = arguments[1];
|
|
3522
|
+
let el = template.render();
|
|
3523
|
+
Globals$1.objToEl.set(obj, el);
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
// h(this)`<div>...</div>
|
|
3527
|
+
else
|
|
3528
|
+
return function(...args) {
|
|
3529
|
+
let template = h(...args);
|
|
3530
|
+
let el = template.render();
|
|
3531
|
+
Globals$1.objToEl.set(obj, el);
|
|
3532
|
+
}.bind(obj);
|
|
3533
|
+
}
|
|
3534
|
+
else
|
|
3535
|
+
throw new Error('h() does not support argument of type: ' + (arguments[0] ? typeof arguments[0] : arguments[0]))
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3364
3538
|
/**
|
|
3365
3539
|
* There are three ways to create an instance of a Solarite Component:
|
|
3366
3540
|
* 1. new ComponentName(); // direct class instantiation
|
|
@@ -3534,7 +3708,7 @@ function createSolarite(extendsTag=null) {
|
|
|
3534
3708
|
|
|
3535
3709
|
BaseClass = Globals$1.elementClasses[extendsTag];
|
|
3536
3710
|
if (!BaseClass) { // TODO: Use Cache
|
|
3537
|
-
BaseClass =
|
|
3711
|
+
BaseClass = Globals$1.doc.createElement(extendsTag).constructor;
|
|
3538
3712
|
Globals$1.elementClasses[extendsTag] = BaseClass;
|
|
3539
3713
|
}
|
|
3540
3714
|
}
|
|
@@ -3595,7 +3769,7 @@ function createSolarite(extendsTag=null) {
|
|
|
3595
3769
|
this.innerHTML = html;
|
|
3596
3770
|
}
|
|
3597
3771
|
else
|
|
3598
|
-
this.modifications =
|
|
3772
|
+
this.modifications = h(this, html, options);
|
|
3599
3773
|
}
|
|
3600
3774
|
})*/
|
|
3601
3775
|
|
|
@@ -3645,11 +3819,14 @@ function createSolarite(extendsTag=null) {
|
|
|
3645
3819
|
let define = 'define';
|
|
3646
3820
|
let getName = 'getName';
|
|
3647
3821
|
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3822
|
+
/*
|
|
3823
|
+
┏┓ ┓ •
|
|
3824
|
+
┗┓┏┓┃┏┓┏┓┓╋▗▖
|
|
3825
|
+
┗┛┗┛┗┗┻╹ ╹╹┗
|
|
3826
|
+
JavasCript UI library
|
|
3827
|
+
@license MIT
|
|
3828
|
+
@copyright Vorticode LLC
|
|
3829
|
+
https://vorticode.github.io/solarite/ */
|
|
3653
3830
|
|
|
3654
3831
|
/**
|
|
3655
3832
|
* TODO: The Proxy and the multiple base classes mess up 'instanceof Solarite'
|
|
@@ -3663,4 +3840,4 @@ const Solarite = new Proxy(createSolarite(), {
|
|
|
3663
3840
|
//export {default as watch, renderWatched} from './watch.js'; // unfinished
|
|
3664
3841
|
|
|
3665
3842
|
export default h;
|
|
3666
|
-
export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template,
|
|
3843
|
+
export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, getArg, h, h as r, setArgs, toEl };
|