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.js CHANGED
@@ -96,8 +96,9 @@ function reset() {
96
96
  Globals = {
97
97
 
98
98
  /**
99
- * Used by NodeGroup.applyComponentExprs() */
100
- componentArgsHash: new WeakMap(),
99
+ * Dynamic values that should be passed to a Component's constructor and render() function.
100
+ * @type {Map<HTMLElement, any[]>} */
101
+ componentArgs: new Map(),
101
102
 
102
103
  /**
103
104
  * Store which instances of Solarite have already been added to the DOM.
@@ -112,6 +113,9 @@ function reset() {
112
113
 
113
114
  div: document.createElement("div"),
114
115
 
116
+ /** @type {HTMLDocument} */
117
+ doc: document,
118
+
115
119
  /**
116
120
  * @type {Record<string, Class<Node>>} A map from built-in tag names to the constructors that create them. */
117
121
  elementClasses: {},
@@ -130,20 +134,20 @@ function reset() {
130
134
  nodeGroups: new WeakMap(),
131
135
 
132
136
  /**
133
- * Used by r() path 9. */
137
+ * Used by h() path 9. */
134
138
  objToEl: new WeakMap(),
135
139
 
136
140
  //pendingChildren: [],
137
141
 
138
142
 
139
143
  /**
140
- * Elements that have been rendered to by r() at least once.
144
+ * Elements that have been rendered to by h() at least once.
141
145
  * This is used by the Solarite class to know when to call onFirstConnect()
142
146
  * @type {WeakSet<HTMLElement>} */
143
147
  rendered: new WeakSet(),
144
148
 
145
149
  /**
146
- * Elements that are currently rendering via the r() function.
150
+ * Elements that are currently rendering via the h() function.
147
151
  * @type {WeakSet<HTMLElement>} */
148
152
  rendering: new WeakSet(),
149
153
 
@@ -230,12 +234,25 @@ let Util = {
230
234
  return true; // the same.
231
235
  },
232
236
 
237
+ /**
238
+ * Convert HTMLElement attributes to an object.
239
+ * @param el {HTMLElement}
240
+ * @param ignore {?string} Optionally ignore this attribute.
241
+ * @return {Object} */
242
+ attribsToObject(el, ignore=null) {
243
+ let result = {};
244
+ for (let attrib of el.attributes)
245
+ if (attrib.name !== ignore)
246
+ result[Util.dashesToCamel(attrib.name)] = attrib.value;
247
+ return result;
248
+ },
249
+
233
250
  bindId(root, el) {
234
251
  let id = el.getAttribute('data-id') || el.getAttribute('id');
235
252
  if (id) { // If something hasn't removed the id.
236
253
 
237
254
  // Don't allow overwriting existing class properties if they already have a non-Node value.
238
- if (root[id] && !(root[id] instanceof Node))
255
+ if (root[id] && !(root[id]?.nodeType))
239
256
  throw new Error(`${root.constructor.name}.${id} already has a value. ` +
240
257
  `Can't set it as a reference to <${el.tagName.toLowerCase()} id="${id}">`);
241
258
 
@@ -244,26 +261,50 @@ let Util = {
244
261
  },
245
262
 
246
263
  /**
264
+ * If the style tab has a global attribute:
265
+ * 1. Put it in the document head as <style data-style="tag-name">...</style>
266
+ * 2. Replace the :host {...} CSS selector as tag-name {...}.
267
+ * Otherwise keep it where it is and:
268
+ * 1. Add data-style="1" attribute to the root element.
269
+ * 2. Replace the :host {...} selector in the style as tag-name[data-style='1'] {...}
247
270
  * @param style {HTMLStyleElement}
248
271
  * @param root {HTMLElement} */
249
272
  bindStyles(style, root) {
250
- let styleId = root.getAttribute('data-style');
251
- if (!styleId) {
252
- // Keep track of one style id for each class.
253
- // TODO: Put this outside the class in a map, so it doesn't conflict with static properties.
254
- if (!root.constructor.styleId)
255
- root.constructor.styleId = 1;
256
- styleId = root.constructor.styleId++;
257
273
 
258
- root.setAttribute('data-style', styleId);
274
+ let tagName = root.tagName.toLowerCase();
275
+ let styleId, attribSelector;
276
+
277
+ if (style.hasAttribute('global') || style.hasAttribute('data-global')) {
278
+ styleId = tagName;
279
+ attribSelector = '';
280
+ let doc = Globals$1.doc || root.ownerDocument || document;
281
+ if (!doc.head.querySelector(`style[data-style="${styleId}"]`)) {
282
+ doc.head.append(style);
283
+ style.setAttribute('data-style', styleId);
284
+ }
285
+ else // TODO: Make sure the style has no expressions.
286
+ style.remove(); // already in the head.
287
+ }
288
+ else {
289
+ let styleId = root.getAttribute('data-style');
290
+ if (!styleId) {
291
+ // Keep track of one style id for each class.
292
+ // TODO: Put this outside the class in a map, so it doesn't conflict with static properties.
293
+ if (!root.constructor.styleId)
294
+ root.constructor.styleId = 1;
295
+ styleId = root.constructor.styleId++;
296
+
297
+ root.setAttribute('data-style', styleId);
298
+ }
299
+
300
+ attribSelector = `[data-style="${styleId}"]`;
259
301
  }
260
302
 
261
303
  // Replace ":host" with "tagName[data-style=...]" in the css.
262
- let tagName = root.tagName.toLowerCase();
263
304
  for (let child of style.childNodes) {
264
305
  if (child.nodeType === 3) {
265
306
  let oldText = child.textContent;
266
- let newText = oldText.replace(/:host(?=[^a-z0-9_])/gi, `${tagName}[data-style="${styleId}"]`);
307
+ let newText = oldText.replace(/:host(?=[^a-z0-9_])/gi, `${tagName}${attribSelector}`);
267
308
  if (oldText !== newText)
268
309
  child.textContent = newText;
269
310
  }
@@ -310,7 +351,6 @@ let Util = {
310
351
  return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
311
352
  },
312
353
 
313
-
314
354
  /**
315
355
  * A generator function that recursively traverses and flattens a value.
316
356
  *
@@ -351,7 +391,7 @@ let Util = {
351
391
 
352
392
  /**
353
393
  * Get the value of an input as the most appropriate JavaScript type.
354
- * @param node {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLDivElement}
394
+ * @param node {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLElement}
355
395
  * @return {string|string[]|number|[]|File[]|Date|boolean} */
356
396
  getInputValue(node) {
357
397
  // .type is a built-in DOM property
@@ -365,6 +405,8 @@ let Util = {
365
405
  return node.valueAsDate; // Date Object
366
406
  if (node.type === 'select-multiple') // <select multiple>
367
407
  return [...node.selectedOptions].map(option => option.value); // Array of Strings
408
+ if (node.hasAttribute('contenteditable'))
409
+ return node.innerHTML;
368
410
 
369
411
  return node.value; // String
370
412
  },
@@ -614,8 +656,6 @@ class MultiValueMap {
614
656
  * @returns {Node[]} The same list of future children.
615
657
  */
616
658
  const udomdiff = (parentNode, a, b, before) => {
617
-
618
-
619
659
  const bLength = b.length;
620
660
  let aEnd = a.length;
621
661
  let bEnd = bLength;
@@ -637,8 +677,6 @@ const udomdiff = (parentNode, a, b, before) => {
637
677
  while (bStart < bEnd) {
638
678
  let bNode = b[bStart++];
639
679
  parentNode.insertBefore(bNode, node);
640
-
641
-
642
680
  }
643
681
  }
644
682
  // remove head or tail: fast path
@@ -648,8 +686,6 @@ const udomdiff = (parentNode, a, b, before) => {
648
686
  let aNode = a[aStart];
649
687
  if (!map || !map.has(aNode)) {
650
688
  parentNode.removeChild(aNode);
651
-
652
-
653
689
  }
654
690
  aStart++;
655
691
  }
@@ -686,13 +722,10 @@ const udomdiff = (parentNode, a, b, before) => {
686
722
  a2,
687
723
  b2.nextSibling
688
724
  );
689
-
690
725
 
691
726
  let bNode = b[--bEnd];
692
727
  parentNode.insertBefore(bNode, node);
693
728
 
694
-
695
-
696
729
  // mark the future index as identical (yeah, it's dirty, but cheap 👍)
697
730
  // The main reason to do this, is that when a[aEnd] will be reached,
698
731
  // the loop will likely be on the fast path, as identical to b[bEnd].
@@ -740,8 +773,6 @@ const udomdiff = (parentNode, a, b, before) => {
740
773
  while (bStart < index) {
741
774
  let bNode = b[bStart++];
742
775
  parentNode.insertBefore(bNode, node);
743
-
744
-
745
776
  }
746
777
  }
747
778
  // if the effort wasn't good enough, fallback to a replace,
@@ -754,8 +785,6 @@ const udomdiff = (parentNode, a, b, before) => {
754
785
  bNode,
755
786
  aNode
756
787
  );
757
-
758
-
759
788
  }
760
789
  }
761
790
  // otherwise move the source forward, 'cause there's nothing to do
@@ -768,8 +797,6 @@ const udomdiff = (parentNode, a, b, before) => {
768
797
  else {
769
798
  let aNode = a[aStart++];
770
799
  parentNode.removeChild(aNode);
771
-
772
-
773
800
  }
774
801
  }
775
802
  }
@@ -893,13 +920,13 @@ class ExprPath {
893
920
  case 2: // PathType.Multiple:
894
921
  this.applyMultipleAttribs(this.nodeMarker, exprs[0]);
895
922
  break;
896
- case 5: // PathType.Comment:
923
+ case 4: // PathType.Comment:
897
924
  // Expressions inside Html comments. Deliberately empty because we won't waste time updating them.
898
925
  break;
899
- case 6: // PathType.Event:
926
+ case 5: // PathType.Event:
900
927
  this.applyEventAttrib(this.nodeMarker, exprs[0], this.parentNg.rootNg.root);
901
928
  break;
902
- default: // TODO: Is this still used? Lots of tests fail without it.
929
+ default: // 3 PathType.Attribute
903
930
  // One attribute value may have multiple expressions. Here we apply them all at once.
904
931
  this.applyValueAttrib(this.nodeMarker, exprs);
905
932
  break;
@@ -909,7 +936,7 @@ class ExprPath {
909
936
  /**
910
937
  * Insert/replace the nodes created by a single expression.
911
938
  * Called by applyExprs()
912
- * This function is recursive, as the functions it calls also call it.
939
+ * This function is recursive. It calls functions that call applyNodes().
913
940
  * @param expr {Expr}
914
941
  * @param freeNodeGroups {boolean}
915
942
  * @return {Node[]} New Nodes created. */
@@ -944,7 +971,6 @@ class ExprPath {
944
971
  if (secondPass.length) {
945
972
  for (let [nodesIndex, ngIndex] of secondPass) {
946
973
  let ng = path.getNodeGroup(newNodes[nodesIndex], false);
947
-
948
974
  let ngNodes = ng.getNodes();
949
975
 
950
976
 
@@ -965,11 +991,8 @@ class ExprPath {
965
991
 
966
992
 
967
993
 
968
-
969
-
970
994
  let oldNodes = path.getNodes();
971
995
 
972
-
973
996
  // This pre-check makes it a few percent faster?
974
997
  let same = Util.arraySame(oldNodes, newNodes);
975
998
  if (!same) {
@@ -982,7 +1005,6 @@ class ExprPath {
982
1005
  // Fast clear method
983
1006
  let isNowEmpty = oldNodes.length && !newNodes.length;
984
1007
  if (!isNowEmpty || !path.fastClear())
985
-
986
1008
  // Rearrange nodes.
987
1009
  udomdiff(path.nodeMarker.parentNode, oldNodes, newNodes, path.nodeMarker);
988
1010
 
@@ -994,22 +1016,18 @@ class ExprPath {
994
1016
  if (!ng.startNode.parentNode)
995
1017
  ng.removeAndSaveOrphans();
996
1018
 
997
-
998
-
999
-
1000
1019
  // Instantiate components created within ${...} expressions.
1001
- // Embedded style tags are handled elsewhere, but where?
1020
+ // Also see this.applyExactNodes() which handles calling render() on web components even if they are unchanged.
1002
1021
  for (let el of newNodes) {
1003
- if (el instanceof HTMLElement) {
1022
+ if (el?.nodeType === 1) { // HTMLElement
1004
1023
  if (el.hasAttribute('solarite-placeholder'))
1005
- this.parentNg.instantiateComponent(el);
1024
+ this.parentNg.handleComponent(el, null, true);
1006
1025
  for (let child of el.querySelectorAll('[solarite-placeholder]'))
1007
- this.parentNg.instantiateComponent(child);
1026
+ this.parentNg.handleComponent(child, null, true);
1008
1027
  }
1009
1028
  }
1010
1029
  }
1011
1030
 
1012
-
1013
1031
 
1014
1032
  }
1015
1033
 
@@ -1117,7 +1135,7 @@ class ExprPath {
1117
1135
  }
1118
1136
 
1119
1137
  // String/Number/Date/Boolean
1120
- else if (!(expr instanceof Template) && !(expr instanceof Node)){
1138
+ else if (!(expr instanceof Template) && !(expr?.nodeType)){
1121
1139
  // Convert expression to a string.
1122
1140
  if (expr === undefined || expr === false || expr === null) // Util.isFalsy() inlined
1123
1141
  expr = '';
@@ -1127,7 +1145,9 @@ class ExprPath {
1127
1145
  // Get the same Template for the same string each time.
1128
1146
  // let template = Globals.stringTemplates[expr];
1129
1147
  // if (!template) {
1148
+
1130
1149
  let template = new Template([expr], []);
1150
+ template.isText = true;
1131
1151
  // Globals.stringTemplates[expr] = template;
1132
1152
  //}
1133
1153
 
@@ -1152,12 +1172,44 @@ class ExprPath {
1152
1172
 
1153
1173
  if (expr instanceof Template) {
1154
1174
  let ng = this.getNodeGroup(expr, true);
1175
+
1155
1176
  if (ng) {
1177
+ let newestNodes = ng.getNodes();
1178
+ newNodes.push(...newestNodes);
1179
+
1180
+ // New!
1181
+ // Re-apply all expressions if there's a web component, so we can pass them to its constructor.
1182
+ // NodeGroup.applyExprs() is used to call applyComponentExprs() on web components that have expression attributes.
1183
+ // For those that don't, we call applyComponentExprs() directly here.
1184
+ // Also see similar code at the end of this.applyNodes() which handles web components being instantiated the first time.
1185
+ let apply = false;
1186
+ for (let el of newestNodes) {
1187
+ if (el?.nodeType === 1) { // HTMLElement
1188
+
1189
+ if (el.tagName.includes('-')) {
1190
+ if (!expr.exprs.find(expr => expr?.nodeMarker === el))
1191
+ this.parentNg.handleComponent(el, null, true);
1192
+ else // Commenting out this "else" causes render() to be called too often, but other UI code fails if it's present.
1193
+ apply = true;
1194
+ }
1195
+ for (let child of el.querySelectorAll('*')) {
1196
+ if (child.tagName.includes('-')) {
1197
+ if (!expr.exprs.find(expr => expr?.nodeMarker === child))
1198
+ this.parentNg.handleComponent(child, null, true);
1199
+ else
1200
+ apply = true;
1201
+ }
1202
+ }
1203
+ }
1204
+ }
1156
1205
 
1157
- // TODO: Track ranges of changed nodes and only pass those to udomdiff?
1158
- // But will that break the swap benchmark?
1159
- newNodes.push(...ng.getNodes());
1206
+ // This calls render() on web components that have expressions as attributes.
1207
+ if (apply)
1208
+ ng.applyExprs(expr.exprs);
1209
+
1160
1210
  this.nodeGroups.push(ng);
1211
+
1212
+ return ng;
1161
1213
  }
1162
1214
 
1163
1215
  // If expression, mark it to be evaluated later in ExprPath.apply() to find partial match.
@@ -1169,10 +1221,10 @@ class ExprPath {
1169
1221
  }
1170
1222
 
1171
1223
  // Node(s) created by an expression.
1172
- else if (expr instanceof Node) {
1224
+ else if (expr?.nodeType) {
1173
1225
 
1174
1226
  // DocumentFragment created by an expression.
1175
- if (expr instanceof DocumentFragment)
1227
+ if (expr?.nodeType === 11) // DocumentFragment
1176
1228
  newNodes.push(...expr.childNodes);
1177
1229
  else
1178
1230
  newNodes.push(expr);
@@ -1204,16 +1256,30 @@ class ExprPath {
1204
1256
  Globals$1.currentExprPath = null;
1205
1257
  }
1206
1258
 
1207
- let attrs = (expr +'') // Split string into multiple attributes.
1208
- .split(/([\w-]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s]+))/g)
1209
- .map(text => text.trim())
1210
- .filter(text => text.length);
1259
+ // Attribute as name: value object.
1260
+ if (typeof expr === 'object') {
1261
+ for (let name in expr) {
1262
+ let value = expr[name];
1263
+ if (value === undefined || value === false || value === null)
1264
+ continue;
1265
+ node.setAttribute(name, value);
1266
+ this.attrNames.add(name);
1267
+ }
1268
+ }
1211
1269
 
1212
- for (let attr of attrs) {
1213
- let [name, value] = attr.split(/\s*=\s*/); // split on first equals.
1214
- value = (value || '').replace(/^(['"])(.*)\1$/, '$2'); // trim value quotes if they match.
1215
- node.setAttribute(name, value);
1216
- this.attrNames.add(name);
1270
+ // Attributes as string
1271
+ else {
1272
+ let attrs = (expr + '') // Split string into multiple attributes.
1273
+ .split(/([\w-]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s]+))/g)
1274
+ .map(text => text.trim())
1275
+ .filter(text => text.length);
1276
+
1277
+ for (let attr of attrs) {
1278
+ let [name, value] = attr.split(/\s*=\s*/); // split on first equals.
1279
+ value = (value || '').replace(/^(['"])(.*)\1$/, '$2'); // trim value quotes if they match.
1280
+ node.setAttribute(name, value);
1281
+ this.attrNames.add(name);
1282
+ }
1217
1283
  }
1218
1284
  }
1219
1285
 
@@ -1312,7 +1378,7 @@ class ExprPath {
1312
1378
  }
1313
1379
 
1314
1380
  /**
1315
- * Handle values, including two-way binding.
1381
+ * Set the value of an attribute. This can be for any attribute, not just attributes named "value".
1316
1382
  * @param node
1317
1383
  * @param exprs */
1318
1384
  // TODO: node is always this.nodeMarker?
@@ -1342,12 +1408,21 @@ class ExprPath {
1342
1408
  }
1343
1409
  else {
1344
1410
  // TODO: should we remove isFalsy, since these are always props?
1345
- let strValue = Util.isFalsy(value) ? '' : value;
1411
+ const strValue = Util.isFalsy(value) ? '' : value;
1346
1412
 
1347
- // If we don't have this condition, when we call render(), the browser will scroll to the currently
1348
- // selected item in a <select> and mess up manually scrolling to a different value.
1349
- if (strValue !== node[this.attrName])
1350
- node[this.attrName] = strValue;
1413
+ // Special case for contenteditable
1414
+ if (this.attrName === 'value' && node.hasAttribute('contenteditable')) {
1415
+ const existingValue = node.innerHTML;
1416
+ if (strValue !== existingValue)
1417
+ node.innerHTML = strValue;
1418
+ }
1419
+ else {
1420
+
1421
+ // If we don't have this condition, when we call render(), the browser will scroll to the currently
1422
+ // selected item in a <select> and mess up manually scrolling to a different value.
1423
+ if (strValue !== node[this.attrName])
1424
+ node[this.attrName] = strValue;
1425
+ }
1351
1426
  }
1352
1427
 
1353
1428
  // TODO: We need to remove any old listeners, like in bindEventAttribute.
@@ -1367,16 +1442,8 @@ class ExprPath {
1367
1442
 
1368
1443
  // Regular attribute
1369
1444
  else {
1370
- // TODO: Cache this on ExprPath.isProp when Shell creates the props. Have ExprPath.clone() copy .isProp
1371
- // Or make it a new PathType.
1372
- //if (this.attrName === 'disabled')
1373
- // debugger;
1374
-
1375
- // hasOwnProperty() checks only the object, not the parents
1376
- // this.attrName in node checks the node and the parents.
1377
- // This version checks the html element it extends from, to see if has a setter set:
1378
- // Object.getOwnPropertyDescriptor(Object.getPrototypeOf(node), this.attrName)?.set
1379
- //let isProp = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(node), this.attrName)?.set;
1445
+ // Cache this on ExprPath.isHtmlProperty when Shell creates the props.
1446
+ // Have ExprPath.clone() copy .isHtmlProperty?
1380
1447
  let isProp = this.isHtmlProperty;
1381
1448
  if (isProp === undefined)
1382
1449
  isProp = this.isHtmlProperty = Util.isHtmlProp(node, this.attrName);
@@ -1386,7 +1453,7 @@ class ExprPath {
1386
1453
  if (!multiple) {
1387
1454
  Globals$1.currentExprPath = this; // Used by watch()
1388
1455
  if (typeof expr === 'function') {
1389
- if (this.type === 4) { // Don't evaluate functions before passing them to components
1456
+ if (this.isComponent) { // Don't evaluate functions before passing them to components
1390
1457
  return
1391
1458
  }
1392
1459
  this.watchFunction = expr; // The function that gets the expression, used for renderWatched()
@@ -1444,6 +1511,13 @@ class ExprPath {
1444
1511
  // since we also prohibit expressions that are a child of textarea.
1445
1512
  if (isProp)
1446
1513
  node[this.attrName] = joinedValue;
1514
+
1515
+ // Allow one-way binding to contenteditable value attribute.
1516
+ // Contenteditables normally don't have a value attribute and have their content set via innerHTML.
1517
+ // Solarite doesn't allow contenteditables to have expressions as their children.
1518
+ else if (node.hasAttribute('contenteditable'))
1519
+ node.innerHTML = joinedValue;
1520
+
1447
1521
  // TODO: Putting an 'else' here would be more performant
1448
1522
  node.setAttribute(this.attrName, joinedValue);
1449
1523
  }
@@ -1474,6 +1548,7 @@ class ExprPath {
1474
1548
  nodeBefore = childNodes[this.nodeBeforeIndex];
1475
1549
 
1476
1550
  let result = new ExprPath(nodeBefore, nodeMarker, this.type, this.attrName, this.attrValue);
1551
+ result.isComponent = this.isComponent;
1477
1552
 
1478
1553
 
1479
1554
 
@@ -1482,9 +1557,7 @@ class ExprPath {
1482
1557
 
1483
1558
  /**
1484
1559
  * Clear the nodeCache of this ExprPath, as well as all parent and child ExprPaths that
1485
- * share the same DOM parent node.
1486
- *
1487
- * TODO: Is recursive clearing ever necessary? */
1560
+ * share the same DOM parent node. */
1488
1561
  clearNodesCache() {
1489
1562
  let path = this;
1490
1563
 
@@ -1497,9 +1570,6 @@ class ExprPath {
1497
1570
  // If stuck in an infinite loop here, the problem is likely due to Template hash colisions.
1498
1571
  // Which cause one path to be the descendant of itself, creating a cycle.
1499
1572
  }
1500
-
1501
- // Commented out on Sep 30, 2024 b/c it was making the benchmark never finish when adding 10k rows.
1502
- //clearChildNodeCache(this);
1503
1573
  }
1504
1574
 
1505
1575
 
@@ -1541,7 +1611,7 @@ class ExprPath {
1541
1611
  // result2.push(...ng.getNodes())
1542
1612
  // return result2;
1543
1613
 
1544
- if (this.type === ExprPathType.AttribValue || this.type === ExprPathType.AttribMultiple || this.type === ExprPathType.ComponentAttribValue) {
1614
+ if (this.type === ExprPathType.AttribValue || this.type === ExprPathType.AttribMultiple) {
1545
1615
  return [this.nodeMarker];
1546
1616
  }
1547
1617
 
@@ -1599,11 +1669,13 @@ class ExprPath {
1599
1669
  result = collection.deleteAny(template.getExactKey());
1600
1670
  }
1601
1671
 
1602
- if (result) // also delete the matching close key.
1672
+ if (result) {// also delete the matching close key.
1603
1673
  collection.deleteSpecific(template.getCloseKey(), result);
1604
- else {
1605
- return null;
1674
+
1675
+ //result.applyExprs(template.exprs);
1606
1676
  }
1677
+ else
1678
+ return null;
1607
1679
  }
1608
1680
 
1609
1681
  // Find a close match.
@@ -1637,12 +1709,6 @@ class ExprPath {
1637
1709
  return result;
1638
1710
  }
1639
1711
 
1640
- isComponent() {
1641
- // Events won't have type===Component.
1642
- // TODO: Have a special flag for components instead of it being on the type?
1643
- return this.type === ExprPathType.ComponentAttribValue || (this.attrName && this.nodeMarker.tagName && this.nodeMarker.tagName.includes('-'));
1644
- }
1645
-
1646
1712
  /**
1647
1713
  * TODO: Rename this to nodeGroupsInUse, nodeGroupsAvialableAttached and nodeGroupsAvailableDetached?
1648
1714
  * Nodes that have been used during the current render().
@@ -1699,7 +1765,7 @@ class ExprPath {
1699
1765
  /** @enum {int} */
1700
1766
  const ExprPathType = {
1701
1767
  /** Child of a node */
1702
- Content: 1,
1768
+ Content: 1, // TODO: Rename to Nodes
1703
1769
 
1704
1770
  /** One or more whole attributes */
1705
1771
  AttribMultiple: 2,
@@ -1707,14 +1773,11 @@ const ExprPathType = {
1707
1773
  /** Value of an attribute. */
1708
1774
  AttribValue: 3,
1709
1775
 
1710
- /** Value of an attribute being passed to a component. */
1711
- ComponentAttribValue: 4,
1712
-
1713
1776
  /** Expressions inside Html comments. */
1714
- Comment: 5,
1777
+ Comment: 4,
1715
1778
 
1716
1779
  /** Value of an attribute. */
1717
- Event: 6,
1780
+ Event: 5,
1718
1781
  };
1719
1782
 
1720
1783
 
@@ -1880,7 +1943,7 @@ class Shell {
1880
1943
 
1881
1944
 
1882
1945
  if (html.length === 1 && !html[0].match(/[<&]/)) {
1883
- this.fragment = document.createTextNode(html[0]);
1946
+ this.fragment = Globals$1.doc.createTextNode(html[0]);
1884
1947
  return;
1885
1948
  }
1886
1949
 
@@ -1888,18 +1951,18 @@ class Shell {
1888
1951
  // 1. Add placeholders
1889
1952
  let joinedHtml = Shell.addPlaceholders(html);
1890
1953
 
1891
- let template = document.createElement('template'); // Using a single global template won't keep the nodes as children of the DocumentFragment.
1954
+ let template = Globals$1.doc.createElement('template'); // Using a single global template won't keep the nodes as children of the DocumentFragment.
1892
1955
  if (joinedHtml)
1893
1956
  template.innerHTML = joinedHtml;
1894
1957
  else // Create one text node, so shell isn't empty and NodeGroups created from it have something to point the startNode and endNode at.
1895
- template.content.append(document.createTextNode(''));
1958
+ template.content.append(Globals$1.doc.createTextNode(''));
1896
1959
  this.fragment = template.content;
1897
1960
 
1898
1961
  // 2. Find placeholders
1899
1962
  let node;
1900
1963
  let toRemove = [];
1901
1964
  let placeholdersUsed = 0;
1902
- const walker = document.createTreeWalker(this.fragment, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT);
1965
+ const walker = Globals$1.doc.createTreeWalker(this.fragment, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT);
1903
1966
  while (node = walker.nextNode()) {
1904
1967
 
1905
1968
  // Remove previous after each iteration, so paths will still be calculated correctly.
@@ -1935,10 +1998,13 @@ class Shell {
1935
1998
  // Replace comment placeholders
1936
1999
  else if (node.nodeType === 8 && node.nodeValue === '!✨!') {
1937
2000
 
2001
+ if (node?.parentNode?.closest && node?.parentNode?.closest('[contenteditable]'))
2002
+ throw new Error(`Contenteditable can't have expressions inside them. Use <div contenteditable value="\${...}"> instead.`);
2003
+
1938
2004
  // Get or create nodeBefore.
1939
2005
  let nodeBefore = node.previousSibling; // Can be the same as another Path's nodeMarker.
1940
2006
  if (!nodeBefore) {
1941
- nodeBefore = document.createComment('ExprPath:'+this.paths.length);
2007
+ nodeBefore = Globals$1.doc.createComment('ExprPath:'+this.paths.length);
1942
2008
  node.parentNode.insertBefore(nodeBefore, node);
1943
2009
  }
1944
2010
 
@@ -1963,9 +2029,9 @@ class Shell {
1963
2029
  placeholdersUsed ++;
1964
2030
  }
1965
2031
 
2032
+ // Comments become text nodes when inside textareas.
1966
2033
  else if (node.nodeType === 3 && node.parentNode?.tagName === 'TEXTAREA' && node.textContent.includes('<!--!✨!-->'))
1967
2034
  throw new Error(`Textarea can't have expressions inside them. Use <textarea value="\${...}"> instead.`);
1968
-
1969
2035
 
1970
2036
 
1971
2037
  // Sometimes users will comment out a block of html code that has expressions.
@@ -1989,7 +2055,7 @@ class Shell {
1989
2055
 
1990
2056
  let placeholders = [];
1991
2057
  for (let i = 0; i<parts.length; i++) {
1992
- let current = document.createTextNode(parts[i]);
2058
+ let current = Globals$1.doc.createTextNode(parts[i]);
1993
2059
  node.parentNode.insertBefore(current, node);
1994
2060
  if (i > 0)
1995
2061
  placeholders.push(current);
@@ -2028,9 +2094,9 @@ class Shell {
2028
2094
  path.nodeMarkerPath = getNodePath(path.nodeMarker);
2029
2095
 
2030
2096
  // Cache so we don't have to calculate this later inside NodeGroup.applyExprs()
2031
- if (path.type === ExprPathType.AttribValue && path.nodeMarker.nodeType === 1 &&
2097
+ if ((path.type === ExprPathType.AttribValue || path.type === ExprPathType.Event) && path.nodeMarker.nodeType === 1 &&
2032
2098
  (path.nodeMarker.tagName.includes('-') || path.nodeMarker.hasAttribute('is'))) {
2033
- path.type = ExprPathType.ComponentAttribValue;
2099
+ path.isComponent = true;
2034
2100
  }
2035
2101
  }
2036
2102
 
@@ -2196,30 +2262,39 @@ class NodeGroup {
2196
2262
  * @type {?Map<HTMLStyleElement, string>} */
2197
2263
  styles;
2198
2264
 
2265
+ dynamicComponents = new Set();
2266
+ staticComponents = [];
2267
+
2268
+ /** @type {Template} */
2269
+ template;
2270
+
2199
2271
 
2200
2272
  /**
2201
2273
  * Create an "instantiated" NodeGroup from a Template and add it to an element.
2202
2274
  * @param template {Template} Create it from the html strings and expressions in this template.
2203
2275
  * @param parentPath {?ExprPath} */
2204
2276
  constructor(template, parentPath=null) {
2277
+ this.rootNg = parentPath?.parentNg?.rootNg || this;
2278
+ this.parentPath = parentPath;
2279
+
2205
2280
  if (!(this instanceof RootNodeGroup)) {
2206
2281
 
2207
- let [fragment, shell] = this.init(template, parentPath);
2282
+ let [fragment, shell] = this.populateFromTemplate(template);
2208
2283
 
2209
2284
  if (fragment && template.exprs.length) {
2210
2285
  this.updatePaths(fragment, shell.paths);
2211
2286
 
2212
2287
  // Static web components can sometimes have children created via expressions.
2213
2288
  // But calling applyExprs() will mess up the shell's path to them.
2214
- // So we find them first, then call activateStaticComponents() after their children have been created.
2215
- let staticComponents = this.findStaticComponents(fragment, shell);
2289
+ // So we find them first, then call instantiateStaticComponents() after their children have been created.
2290
+ this.staticComponents = this.findStaticComponents(fragment, shell);
2216
2291
 
2217
2292
  this.activateEmbeds(fragment, shell);
2218
2293
 
2219
2294
  // Apply exprs
2220
2295
  this.applyExprs(template.exprs);
2221
2296
 
2222
- this.instantiateStaticComponents(staticComponents);
2297
+ this.instantiateStaticComponents(this.staticComponents);
2223
2298
  }
2224
2299
  else if (shell)
2225
2300
  this.activateEmbeds(fragment, shell);
@@ -2230,47 +2305,33 @@ class NodeGroup {
2230
2305
  * Common init shared by RootNodeGroup and NodeGroup constructors.
2231
2306
  * But in a separate function because they need to do this at a different step.
2232
2307
  * @param template {Template} Create it from the html strings and expressions in this template.
2233
- * @param parentPath {?ExprPath}
2234
- * @param exactKey {?string} Optional, if already calculated.
2235
- * @param closeKey {?string}
2236
- * @returns {[DocumentFragment, Shell]} */
2237
- init(template, parentPath=null, exactKey=null, closeKey=null) {
2238
- this.exactKey = exactKey || template.getExactKey();
2239
- this.closeKey = closeKey || template.getCloseKey();
2240
-
2241
- this.parentPath = parentPath;
2242
- this.rootNg = parentPath?.parentNg?.rootNg || this;
2243
-
2308
+ * @returns {[DocumentFragment, Shell]} The Shell created from the template,a nd the fragment cloned from the Shell.*/
2309
+ populateFromTemplate(template) {
2244
2310
 
2245
-
2246
- /** @type {Template} */
2247
2311
  this.template = template;
2248
-
2249
- // new! Is this needed?
2250
- template.nodeGroup = this;
2251
-
2252
- // Get a cached version of the parsed and instantiated html, and ExprPaths.
2312
+ this.exactKey = template.getExactKey();
2313
+ this.closeKey = template.getCloseKey();
2253
2314
 
2254
2315
  // If it's just a text node, skip a bunch of unnecessary steps.
2255
- if (!(this instanceof RootNodeGroup) && !template.exprs.length && !template.html[0].includes('<')) {
2256
- //let doc = this.rootNg.startNode?.ownerDocument || document;
2257
- let textNode = document.createTextNode(template.html[0]);
2258
-
2316
+ if (template.isText) {
2317
+ let textNode = Globals$1.doc.createTextNode(template.html[0]);
2259
2318
  this.startNode = this.endNode = textNode;
2260
2319
  return [];
2261
2320
  }
2321
+
2322
+ // Get a cached version of the parsed and instantiated html, and ExprPaths:
2262
2323
  else {
2263
2324
  let shell = Shell.get(template.html);
2264
2325
  let fragment = shell.fragment.cloneNode(true);
2265
2326
 
2266
- if (fragment instanceof DocumentFragment) {
2327
+ if (fragment?.nodeType === 11) { // DocumentFragment
2267
2328
  let childNodes = fragment.childNodes;
2268
2329
  this.startNode = childNodes[0];
2269
2330
  this.endNode = childNodes[childNodes.length - 1];
2270
2331
  }
2271
- else {
2332
+ else
2272
2333
  this.startNode = this.endNode = fragment;
2273
- }
2334
+
2274
2335
  return [fragment, shell];
2275
2336
  }
2276
2337
  }
@@ -2308,6 +2369,7 @@ class NodeGroup {
2308
2369
  exprIndex--;
2309
2370
  }
2310
2371
 
2372
+
2311
2373
  // TODO: Need to end and restart this block when going from one component to the next?
2312
2374
  // Think of having two adjacent components.
2313
2375
  // But the dynamicAttribsAdjacet test already passes.
@@ -2316,11 +2378,11 @@ class NodeGroup {
2316
2378
  // 1. Instantiate it if it hasn't already been, sending all expr's to its constructor.
2317
2379
  // 2. Otherwise send them to its render function.
2318
2380
  // Components with no expressions as attributes are instead activated in activateEmbeds().
2319
- if (path.nodeMarker !== this.rootNg.root && path.isComponent()) {
2381
+ if (path.nodeMarker !== this.rootNg.root && path.isComponent) {
2320
2382
 
2321
- if (!nextPath || !nextPath.isComponent() || nextPath.nodeMarker !== path.nodeMarker)
2383
+ if (!nextPath || !nextPath.isComponent || nextPath.nodeMarker !== path.nodeMarker)
2322
2384
  lastComponentPathIndex = i;
2323
- let isFirstComponentPath = !prevPath || !prevPath.isComponent() || prevPath.nodeMarker !== path.nodeMarker;
2385
+ let isFirstComponentPath = !prevPath || !prevPath.isComponent || prevPath.nodeMarker !== path.nodeMarker;
2324
2386
 
2325
2387
  if (isFirstComponentPath) {
2326
2388
 
@@ -2330,7 +2392,7 @@ class NodeGroup {
2330
2392
  componentProps[attrName] = pathExprs[j].length > 1 ? pathExprs[j].join('') : pathExprs[j][0];
2331
2393
  }
2332
2394
 
2333
- this.applyComponentExprs(path.nodeMarker, componentProps);
2395
+ this.handleComponent(path.nodeMarker, componentProps, true);
2334
2396
 
2335
2397
  // Set attributes on component.
2336
2398
  for (let j=i; j<=lastComponentPathIndex; j++)
@@ -2349,7 +2411,10 @@ class NodeGroup {
2349
2411
  // TODO: Only do this if we have ExprPaths within styles?
2350
2412
  this.updateStyles();
2351
2413
 
2352
-
2414
+ // Call render() on static web components. This makes the component.staticAttribs() test work.
2415
+ for (let el of this.staticComponents)
2416
+ if (el.render)
2417
+ el.render(Util.attribsToObject(el)); // It has no expressions.
2353
2418
 
2354
2419
  // Invalidate the nodes cache because we just changed it.
2355
2420
  this.nodesCache = null;
@@ -2363,50 +2428,39 @@ class NodeGroup {
2363
2428
  }
2364
2429
 
2365
2430
  /**
2366
- * Create a nested Component or call render with the new props.
2367
- * @param el {Solarite:HTMLElement}
2368
- * @param props {Object} */
2369
- applyComponentExprs(el, props) {
2370
-
2371
- // TODO: Does a hash of this already exist somewhere?
2372
- // Perhaps if Components were treated as child NodeGroups, which would need to be the child of an ExprPath,
2373
- // then we could re-use the hash and logic from NodeManager?
2374
- let newHash = getObjectHash(props);
2375
-
2431
+ * Unified path to ensure a child component is instantiated (if placeholder) and optionally rendered.
2432
+ * @param el {HTMLElement}
2433
+ * @param props {?Object}
2434
+ * @param doRender {boolean}
2435
+ * @return {HTMLElement} The (possibly replaced) element. */
2436
+ handleComponent(el, props=null, doRender=true) {
2376
2437
  let isPreHtmlElement = el.hasAttribute('solarite-placeholder');
2377
2438
  let isPreIsElement = el.hasAttribute('_is');
2378
-
2379
-
2380
- // Instantiate a placeholder.
2439
+ let attribs, children;
2381
2440
  if (isPreHtmlElement || isPreIsElement)
2382
- el = this.instantiateComponent(el, isPreHtmlElement, props);
2383
-
2384
- // Call render() with the same params that would've been passed to the constructor.
2385
- // We do this even if the arguments haven't changed, so we can let the child component
2386
- // compare the arguments and then decide for itself whether it wants to re-render.
2387
- else if (el.render) {
2388
- //let oldHash = Globals.componentArgsHash.get(el);
2389
- //if (oldHash !== newHash) { // Only if not changed.
2390
- let args = {};
2441
+ [el, attribs, children] = this.instantiateComponent(el, isPreHtmlElement, props);
2442
+ if (doRender && el.render) {
2443
+ if (!attribs) {
2444
+ attribs = Util.attribsToObject(el);
2391
2445
  for (let name in props || {})
2392
- args[Util.dashesToCamel(name)] = props[name];
2393
- el.render(args); // Pass new values of props to render so it can decide how it wants to respond.
2394
- //}
2446
+ attribs[Util.dashesToCamel(name)] = props[name];
2447
+ children = el.childNodes;
2448
+ }
2449
+ el.render(attribs, children);
2395
2450
  }
2396
-
2397
- Globals$1.componentArgsHash.set(el, newHash);
2451
+ return el;
2398
2452
  }
2399
-
2453
+
2400
2454
  /**
2401
2455
  * We swap the placeholder element for the real element so we can pass its dynamic attributes
2402
2456
  * to its constructor.
2457
+ * This is only called by handleComponent()
2458
+ * This does not call render()
2403
2459
  *
2404
- * The logic of this function is complex and could use cleaning up.
2405
- *
2406
- * @param el
2460
+ * @param el {HTMLElement}
2407
2461
  * @param isPreHtmlElement {?boolean} True if the element's tag name ends with -solarite-placeholder
2408
2462
  * @param props {Object} Attributes with dynamic values.
2409
- * @return {HTMLElement} */
2463
+ * @return {[HTMLElement, attribs:Object, children:Node[]]}} */
2410
2464
  instantiateComponent(el, isPreHtmlElement=undefined, props=undefined) {
2411
2465
  if (isPreHtmlElement === undefined)
2412
2466
  isPreHtmlElement = !el.hasAttribute('_is');
@@ -2421,24 +2475,17 @@ class NodeGroup {
2421
2475
  if (!Constructor)
2422
2476
  throw new Error(`The custom tag name ${tagName} is not registered.`)
2423
2477
 
2424
- let args = {};
2425
- for (let name in props || {})
2426
- args[Util.dashesToCamel(name)] = props[name];
2427
-
2428
2478
  // Pass other attribs to constructor, since otherwise they're not yet set on the element,
2429
2479
  // and the constructor would otherwise have no way to see them.
2430
- if (el.attributes.length) {
2431
- for (let attrib of el.attributes) {
2432
- let attribName = Util.dashesToCamel(attrib.name);
2433
- if (!args.hasOwnProperty(attribName) && attribName !== 'solarite-placeholder')
2434
- args[attribName] = attrib.value;
2435
- }
2436
- }
2480
+ let attribs = Util.attribsToObject(el, 'solarite-placeholder');
2481
+ for (let name in props || {})
2482
+ attribs[Util.dashesToCamel(name)] = props[name];
2483
+
2437
2484
 
2438
2485
  // Create the web component.
2439
2486
  // Get the children that aren't Solarite's comment placeholders.
2440
- let ch = [...el.childNodes].filter(node => node.nodeType !== Node.COMMENT_NODE || !node.nodeValue.startsWith('ExprPath'));
2441
- let newEl = new Constructor(args, ch);
2487
+ let children = [...el.childNodes].filter(node => node.nodeType !== Node.COMMENT_NODE || !node.nodeValue.startsWith('ExprPath'));
2488
+ let newEl = new Constructor(attribs, children);
2442
2489
 
2443
2490
  if (!isPreHtmlElement)
2444
2491
  newEl.setAttribute('is', el.getAttribute('is').toLowerCase());
@@ -2464,7 +2511,7 @@ class NodeGroup {
2464
2511
  if (this.endNode === el)
2465
2512
  this.endNode = newEl;
2466
2513
 
2467
-
2514
+ // This is used only if inheriting from the Solarite class.
2468
2515
  // applyComponentExprs() is called because we're rendering.
2469
2516
  // So we want to render the sub-component also.
2470
2517
  if (newEl.renderFirstTime)
@@ -2488,7 +2535,7 @@ class NodeGroup {
2488
2535
  newEl.setAttribute(name, val);
2489
2536
  }
2490
2537
 
2491
- return newEl;
2538
+ return [newEl, attribs, children];
2492
2539
  }
2493
2540
 
2494
2541
  /**
@@ -2535,18 +2582,21 @@ class NodeGroup {
2535
2582
  * Requires the nodeCache to be present. */
2536
2583
  removeAndSaveOrphans() {
2537
2584
 
2538
- let fragment = document.createDocumentFragment();
2585
+ let fragment = Globals$1.doc.createDocumentFragment();
2539
2586
  for (let node of this.getNodes())
2540
2587
  fragment.append(node);
2541
2588
  }
2542
2589
 
2543
2590
 
2544
- updatePaths(fragment, paths, offset) {
2545
- // Update paths to point to the fragment.
2591
+ /**
2592
+ * @param fragment {DocumentFragment}
2593
+ * @param paths
2594
+ * @param startingPathDepth {int} */
2595
+ updatePaths(fragment, paths, startingPathDepth) {
2546
2596
  let pathLength = paths.length;
2547
2597
  this.paths.length = pathLength;
2548
2598
  for (let i=0; i<pathLength; i++) {
2549
- let path = paths[i].clone(fragment, offset);
2599
+ let path = paths[i].clone(fragment, startingPathDepth);
2550
2600
  path.parentNg = this;
2551
2601
  this.paths[i] = path;
2552
2602
  }
@@ -2563,7 +2613,7 @@ class NodeGroup {
2563
2613
 
2564
2614
 
2565
2615
 
2566
- findStaticComponents(root, shell, pathOffset=0) {
2616
+ findStaticComponents(root, shell, startingPathDepth=0) {
2567
2617
  let result = [];
2568
2618
 
2569
2619
  // static components. These are WebComponents that do not have any constructor arguments that are expressions.
@@ -2571,8 +2621,8 @@ class NodeGroup {
2571
2621
  // Maybe someday these two paths will be merged?
2572
2622
  // Must happen before ids because instantiateComponent will replace the element.
2573
2623
  for (let path of shell.staticComponents) {
2574
- if (pathOffset)
2575
- path = path.slice(0, -pathOffset);
2624
+ if (startingPathDepth)
2625
+ path = path.slice(0, -startingPathDepth);
2576
2626
  let el = resolveNodePath(root, path);
2577
2627
 
2578
2628
  // Shell doesn't know if a web component is the pseudoRoot so we have to detect it here.
@@ -2584,8 +2634,9 @@ class NodeGroup {
2584
2634
  }
2585
2635
 
2586
2636
  instantiateStaticComponents(staticComponents) {
2587
- for (let el of staticComponents)
2588
- this.instantiateComponent(el);
2637
+ // TODO: Why do we not call render() on the static component here? The tests pass either way.
2638
+ for (let i in staticComponents)
2639
+ staticComponents[i] = this.handleComponent(staticComponents[i], null, false);
2589
2640
  }
2590
2641
 
2591
2642
  /**
@@ -2606,7 +2657,7 @@ class NodeGroup {
2606
2657
  let el = resolveNodePath(root, path);
2607
2658
  Util.bindId(rootEl, el);
2608
2659
  }
2609
- }
2660
+ }
2610
2661
 
2611
2662
  // styles
2612
2663
  if (options?.styles !== false) {
@@ -2636,9 +2687,8 @@ class NodeGroup {
2636
2687
  }
2637
2688
  }
2638
2689
  }
2639
- }
2640
-
2641
-
2690
+ }
2691
+
2642
2692
  class RootNodeGroup extends NodeGroup {
2643
2693
 
2644
2694
  /**
@@ -2652,18 +2702,18 @@ class RootNodeGroup extends NodeGroup {
2652
2702
  exprsToRender = new Map();
2653
2703
 
2654
2704
  /**
2655
- *
2656
- * @param template
2657
- * @param el
2658
- * @param options {?object}
2659
- */
2705
+ * @param template {Template}
2706
+ * @param el {?HTMLElement} Optional, pre-existing htmlElement tat will be the root.
2707
+ * @param options {?object} */
2660
2708
  constructor(template, el, options) {
2661
2709
  super(template);
2662
2710
 
2663
2711
  this.options = options;
2664
2712
 
2665
- this.rootNg = this;
2666
- let [fragment, shell] = this.init(template);
2713
+ let [fragment, shell] = this.populateFromTemplate(template);
2714
+
2715
+ let startingPathDepth = 0;
2716
+
2667
2717
 
2668
2718
  if (fragment instanceof Text) {
2669
2719
 
@@ -2674,25 +2724,25 @@ class RootNodeGroup extends NodeGroup {
2674
2724
  el.append(fragment);
2675
2725
  this.root = el;
2676
2726
  }
2727
+ else
2728
+ throw new Error('Cannot create a standalone text node');
2677
2729
  Globals$1.nodeGroups.set(this.root, this);
2678
2730
  }
2731
+
2732
+
2679
2733
  else {
2680
2734
 
2681
2735
  // If adding NodeGroup to an element.
2682
- let offset = 0;
2683
- let root = fragment; // TODO: Rename so it's not confused with this.root.
2684
2736
  if (el) {
2685
- Globals$1.nodeGroups.set(el, this);
2737
+ this.root = el;
2686
2738
 
2687
2739
  // Save slot children
2688
2740
  let slotChildren;
2689
2741
  if (el.childNodes.length) {
2690
- slotChildren = document.createDocumentFragment();
2742
+ slotChildren = Globals$1.doc.createDocumentFragment();
2691
2743
  slotChildren.append(...el.childNodes);
2692
2744
  }
2693
2745
 
2694
- this.root = el;
2695
-
2696
2746
  // If el should replace the root node of the fragment.
2697
2747
  if (isReplaceEl(fragment, el)) {
2698
2748
  el.append(...fragment.children[0].childNodes);
@@ -2703,8 +2753,10 @@ class RootNodeGroup extends NodeGroup {
2703
2753
  el.setAttribute(attrib.name, attrib.value);
2704
2754
 
2705
2755
  // Go one level deeper into all of shell's paths.
2706
- offset = 1;
2707
- } else {
2756
+ startingPathDepth = 1;
2757
+ }
2758
+
2759
+ else {
2708
2760
  let isEmpty = fragment.childNodes.length === 1 && fragment.childNodes[0].nodeType === 3 && fragment.childNodes[0].textContent === '';
2709
2761
  if (!isEmpty)
2710
2762
  el.append(...fragment.childNodes);
@@ -2732,35 +2784,35 @@ class RootNodeGroup extends NodeGroup {
2732
2784
  el.append(slotChildren);
2733
2785
  }
2734
2786
 
2735
- root = el;
2736
-
2737
2787
  this.startNode = el;
2738
2788
  this.endNode = el;
2739
- } else {
2789
+ }
2790
+
2791
+ // Instantiate as a standalone element.
2792
+ else {
2740
2793
  let singleEl = getSingleEl(fragment);
2741
- this.root = singleEl || fragment; // We return the whole fragment when calling r() with a collection of nodes.
2794
+ this.root = singleEl || fragment; // We return the whole fragment when calling h() with a collection of nodes.
2742
2795
 
2743
- Globals$1.nodeGroups.set(this.root, this);
2744
- if (singleEl) {
2745
- root = singleEl;
2746
- offset = 1;
2747
- }
2796
+ if (singleEl)
2797
+ startingPathDepth = 1;
2748
2798
  }
2749
-
2750
- this.updatePaths(root, shell.paths, offset);
2799
+ Globals$1.nodeGroups.set(this.root, this);
2800
+ this.updatePaths(this.root, shell.paths, startingPathDepth);
2751
2801
 
2752
2802
  // Static web components can sometimes have children created via expressions.
2753
2803
  // But calling applyExprs() will mess up the shell's path to them.
2754
2804
  // So we find them first, then call activateStaticComponents() after their children have been created.
2755
- let staticComponents = this.findStaticComponents(root, shell, offset);
2805
+ this.staticComponents = this.findStaticComponents(this.root, shell, startingPathDepth);
2756
2806
 
2757
- this.activateEmbeds(root, shell, offset);
2807
+ this.activateEmbeds(this.root, shell, startingPathDepth);
2758
2808
 
2759
2809
  // Apply exprs
2760
2810
  this.applyExprs(template.exprs);
2761
2811
 
2762
- this.instantiateStaticComponents(staticComponents);
2812
+ this.instantiateStaticComponents(this.staticComponents);
2763
2813
  }
2814
+
2815
+
2764
2816
  }
2765
2817
  }
2766
2818
 
@@ -2802,8 +2854,7 @@ class Template {
2802
2854
  /** @type {Array} Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
2803
2855
  hashedFields;
2804
2856
 
2805
- /** @type {NodeGroup} */
2806
- nodeGroup;
2857
+ isText;
2807
2858
 
2808
2859
  /**
2809
2860
  *
@@ -2899,169 +2950,203 @@ class Template {
2899
2950
 
2900
2951
  return this.closeKey;
2901
2952
  }
2902
- }
2903
2953
 
2954
+ /**
2955
+ * @param tag {string}
2956
+ * @param props {?Record<string, any>}
2957
+ * @param children
2958
+ * @returns {Template} */
2959
+ static fromJsx(tag, props, children) {
2904
2960
 
2905
- /**
2906
- * @typedef {Object} RenderOptions
2907
- * @property {boolean=} styles - Replace :host in style tags to scope them locally.
2908
- * @property {boolean=} scripts - Execute script tags.
2909
- * @property {boolean=} ids - Create references to elements with id or data-id attributes.
2910
- * @property {?boolean} render - Deprecated.
2911
- * Used only when options are given to a class super constructor inheriting from Solarite.
2912
- * True to call render() immediately in super constructor.
2913
- * False to automatically call render() at all.
2914
- * Undefined (default) to call render() when added to the DOM, unless already rendered.
2915
- */
2916
-
2917
- /**
2918
- * Convert strings to HTMLNodes.
2919
- * Using h`...` as a tag will always create a Template.
2920
- * Using h() as a function() will always create a DOM element.
2921
- *
2922
- * Features beyond what standard js tagged template strings do:
2923
- * 1. r`` sub-expressions
2924
- * 2. functions, nodes, and arrays of nodes as sub-expressions.
2925
- * 3. html-escape all expressions by default, unless wrapped in r()
2926
- * 4. event binding
2927
- * 5. TODO: list more
2928
- *
2929
- * Currently supported:
2930
- * 1. h(el, options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
2931
- * 2. h(el, template, ?options) // Render the Template created by #1 to element.
2932
- *
2933
- * 3. h`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
2934
- *
2935
- * 4. h('Hello'); // Create single text node.
2936
- * 5. h('<b>Hello</b>'); // Create single HTMLElement
2937
- * 6. h('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
2938
- * 7. h()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which
2939
- * // includes properly handling nested components and r`` sub-expressions.
2940
- * 8. h(template) // Render Template created by #1.
2941
- *
2942
- * 9. h({render(){...}}) // Pass an object with a render method, and optionally other props/methods.
2943
- * 10. h(string, object, ...) // JSX TODO
2944
- * @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
2945
- * @param exprs {*[]|string|Template|Object}
2946
- * @return {Node|HTMLElement|Template} */
2947
- function h(htmlStrings=undefined, ...exprs) {
2961
+ // HTML void elements that must not have closing tags
2962
+ const isVoid = selfClosingTags.has(tag.toLowerCase());
2948
2963
 
2949
- if (htmlStrings === undefined && !exprs.length && arguments.length)
2950
- throw new Error('h() cannot be called with undefined.');
2964
+ // Build htmlStrings/exprs so Shell can place placeholders in attribute values and child content.
2965
+ let htmlStrings = [];
2966
+ let templateExprs = [];
2951
2967
 
2952
- // TODO: Make this a more flat if/else and call other functions for the logic.
2953
- if (htmlStrings instanceof Node) {
2954
- let parent = htmlStrings, template = exprs[0];
2968
+ // Opening tag
2969
+ let open = `<${tag}`;
2955
2970
 
2956
- // 1
2957
- if (!(exprs[0] instanceof Template)) {
2958
- if (parent.shadowRoot)
2959
- parent.innerHTML = ''; // Remove shadowroot. TODO: This could mess up paths?
2971
+ // Attributes
2972
+ if (props && typeof props === 'object') {
2973
+ for (let name in props) {
2974
+ let value = props[name];
2960
2975
 
2961
- let options = exprs[0];
2976
+ // id and data-id are static in templates — never expressions
2977
+ if (name === 'id' || name === 'data-id') {
2978
+ // Write directly into the opening string with quotes
2979
+ open += ` ${name}="${value}"`;
2980
+ continue;
2981
+ }
2962
2982
 
2963
- // Return a tagged template function that applies the tagged themplate to parent.
2964
- let taggedTemplate = (htmlStrings, ...exprs) => {
2965
- Globals$1.rendered.add(parent);
2966
- let template = new Template(htmlStrings, exprs);
2967
- return template.render(parent, options);
2968
- };
2969
- return taggedTemplate;
2983
+ // Dynamic attribute value: functions are unquoted (e.g., onclick=${fn}), others quoted
2984
+ if (typeof value === 'function') {
2985
+ open += ` ${name}=`;
2986
+ htmlStrings.push(open);
2987
+ templateExprs.push(value);
2988
+ // reset so subsequent attributes start fresh (e.g., ' title=')
2989
+ open = ``;
2990
+ }
2991
+ else {
2992
+ open += ` ${name}=`;
2993
+ htmlStrings.push(open);
2994
+ templateExprs.push(value);
2995
+ // reset so subsequent attributes start fresh (e.g., ' title=')
2996
+ open = ``;
2997
+ }
2998
+ }
2970
2999
  }
2971
3000
 
2972
- // 2. Render template created by #4 to element.
2973
- else { // instanceof Template
2974
- let options = exprs[1];
2975
- template.render(parent, options);
2976
-
2977
- // Append on the first go.
2978
- if (!parent.childNodes.length && this) {
2979
- // TODO: Is this ever executed?
2980
- debugger;
2981
- parent.append(this.rootNg.getParentNode());
3001
+ // Finalize opening tag precisely to match tagged template splitting
3002
+ if (!isVoid) {
3003
+ const pushedAny = htmlStrings.length > 0;
3004
+ // If nothing pushed yet (no dynamic attrs), push the entire open + '>'
3005
+ if (!pushedAny)
3006
+ htmlStrings.push(open + '>');
3007
+ else {
3008
+ // If we were in a quoted attr (open === '"'), then the string after expr is '">' ;
3009
+ // Otherwise (function-valued attr), the string after expr is just '>'
3010
+ htmlStrings.push(open === '"' ? '">' : '>');
2982
3011
  }
3012
+
3013
+ for (let child of children)
3014
+ addChild(child, htmlStrings, templateExprs);
2983
3015
  }
2984
- }
2985
3016
 
2986
- // 3. Path if used as a template tag.
2987
- else if (Array.isArray(htmlStrings)) {
2988
- return new Template(htmlStrings, exprs);
3017
+ // Closing tag (not for void tags)
3018
+ if (!isVoid) {
3019
+ // If we never emitted the '>' for the open tag (no children were added),
3020
+ // then it was appended above before children. Now just add the closing tag to the last html segment.
3021
+ let lastIdx = htmlStrings.length - 1;
3022
+ htmlStrings[lastIdx] += `</${tag}>`;
3023
+ }
3024
+ else {
3025
+ // Void element: ensure we emitted a trailing '>' segment
3026
+ const pushedAny = htmlStrings.length > 0;
3027
+ if (!pushedAny)
3028
+ htmlStrings.push(open + '>');
3029
+ else
3030
+ htmlStrings.push('>');
3031
+ }
3032
+
3033
+ // Ensure invariant
3034
+ //assert(htmlStrings.length === templateExprs.length + 1);
3035
+ //console.log([htmlStrings, templateExprs])
3036
+ return new Template(htmlStrings, templateExprs);
2989
3037
  }
3038
+ }
2990
3039
 
2991
- else if (typeof htmlStrings === 'string' || htmlStrings instanceof String) {
2992
- // 10. JSX
2993
- if (typeof exprs[0] === 'object') {
2994
- exprs[0] || {};
2995
- exprs.slice(1);
2996
3040
 
2997
- let templateHtmlStrings = [];
2998
- let templateExprs = [];
3041
+ const selfClosingTags = new Set(['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr']);
2999
3042
 
3000
- // TODO How to know which children are static html and which are expression placeholders?
3001
- // Perhaps we have to treat every text child as a string?
3002
3043
 
3003
- assert(templateHtmlStrings.length === templateExprs.length+1);
3004
- return new Template(templateHtmlStrings, templateExprs);
3044
+ /**
3045
+ * Add child Templates that were already created via h() and Template.fromJsx()
3046
+ * @param template {Template}
3047
+ * @param html {string[]}
3048
+ * @param exprs {any[]} */
3049
+ const addChild = (template, html, exprs) => {
3050
+
3051
+ if (Array.isArray(template)) {
3052
+ for (let c of template)
3053
+ addChild(c, html, exprs);
3054
+ }
3055
+ else {
3056
+ let flatten = false;
3057
+ if (template instanceof Template) {
3058
+ // Heuristic to match tagged-template splitting:
3059
+ // - Flatten if the child has expressions (so JSX can inline attribute/value placeholders like tagged literals would).
3060
+ // - Also flatten void elements (e.g., <img>) so they inline like literals.
3061
+ // - Otherwise, keep as a dynamic child placeholder to match cases where the tagged template used an expression child.
3062
+ const childHasExprs = template.exprs.length > 0;
3063
+ if (childHasExprs)
3064
+ flatten = true;
3065
+ else {
3066
+ const m = (template.html[0] || '').match(/^<([a-zA-Z][\w:-]*)/);
3067
+ const childTag = m ? m[1].toLowerCase() : '';
3068
+ flatten = selfClosingTags.has(childTag);
3069
+ }
3005
3070
  }
3006
3071
 
3072
+ if (flatten) {
3073
+ // Flatten/interleave into current segment to match tagged template splitting
3074
+ html[html.length - 1] += template.html[0];
3075
+ for (let i = 0; i < template.exprs.length; i++) {
3076
+ exprs.push(template.exprs[i]);
3077
+ html.push(template.html[i + 1] ?? '');
3078
+ }
3079
+ } else {
3080
+ // Keep as dynamic child
3081
+ exprs.push(template);
3082
+ html.push('');
3083
+ }
3084
+ }
3085
+ };
3007
3086
 
3008
- // If it starts with a string, trim both ends.
3009
- // TODO: Also trim if it ends with whitespace?
3010
- if (htmlStrings.match(/^\s^</))
3011
- htmlStrings = htmlStrings.trim();
3087
+
3088
+ /**
3089
+ * @typedef {Object} RenderOptions
3090
+ * @property {boolean=} styles - Replace :host in style tags to scope them locally.
3091
+ * @property {boolean=} scripts - Execute script tags.
3092
+ * @property {boolean=} ids - Create references to elements with id or data-id attributes.
3093
+ * @property {?boolean} render - Deprecated.
3094
+ * Used only when options are given to a class super constructor inheriting from Solarite.
3095
+ * True to call render() immediately in super constructor.
3096
+ * False to automatically call render() at all.
3097
+ * Undefined (default) to call render() when added to the DOM, unless already rendered.
3098
+ */
3099
+
3100
+ /**
3101
+ * Convert a template, string, or object into a DOM Node or Element
3102
+ *
3103
+ * 1. h('Hello'); // Create single text node.
3104
+ * 2. h('<b>Hello</b>'); // Create single HTMLElement
3105
+ * 3. h('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
3106
+ * 4. h(template) // Render Template created by h`<html>` or h();
3107
+ * 5. h({render(){...}}) // Pass an object with a render method, and optionally other props/methods.
3108
+ * @param arg {string|Template|{render:()=>void}}
3109
+ * @returns {Node|DocumentFragment|HTMLElement} */
3110
+ function toEl(arg) {
3111
+
3112
+ if (typeof arg === 'string') {
3113
+ let html = arg;
3114
+
3115
+ // If it's an element with whitespace before or after it, trim both ends.
3116
+ if (html.match(/^\s^</) || html.match(/>\s+$/))
3117
+ html = html.trim();
3012
3118
 
3013
3119
  // We create a new one each time because otherwise
3014
3120
  // the returned fragment will have its content replaced by a subsequent call.
3015
- let templateEl = document.createElement('template');
3016
- templateEl.innerHTML = htmlStrings;
3121
+ let templateEl = Globals$1.doc.createElement('template');
3122
+ templateEl.innerHTML = html;
3017
3123
 
3018
- // 4+5. Return Node if there's one child.
3124
+ // 1+2. Return Node if there's one child.
3019
3125
  let relevantNodes = Util.trimEmptyNodes(templateEl.content.childNodes);
3020
3126
  if (relevantNodes.length === 1)
3021
3127
  return relevantNodes[0];
3022
3128
 
3023
- // 6. Otherwise return DocumentFragment.
3129
+ // 3. Otherwise return DocumentFragment.
3024
3130
  return templateEl.content;
3025
3131
  }
3026
3132
 
3027
- // 7. Create a static element
3028
- else if (htmlStrings === undefined) {
3029
- return (htmlStrings, ...exprs) => {
3030
- //Globals.rendered.add(parent)
3031
- let template = h(htmlStrings, ...exprs);
3032
- return template.render();
3033
- }
3034
- }
3035
-
3036
- // 8.
3037
- else if (htmlStrings instanceof Template) {
3038
- return htmlStrings.render();
3133
+ // 4.
3134
+ if (arg instanceof Template) {
3135
+ return arg.render();
3039
3136
  }
3040
3137
 
3041
-
3042
- // 9. Create dynamic element with render() function.
3138
+ // 5. Create dynamic element from an object with a render() function.
3043
3139
  // TODO: This path doesn't handle embeds like data-id="..."
3044
- else if (typeof htmlStrings === 'object') {
3045
- let obj = htmlStrings;
3140
+ else if (arg && typeof arg === 'object') {
3141
+ let obj = arg;
3046
3142
 
3047
- if (obj.constructor.name !== 'Object')
3143
+ if (obj.constructor.name !== 'Object')
3048
3144
  throw new Error(`Solarate Web Component class ${obj.constructor?.name} must extend HTMLElement.`);
3049
3145
 
3050
-
3051
- // Special rebound render path, called by normal path.
3052
- // Intercepts the main r`...` function call inside render().
3053
- if (Globals$1.objToEl.has(obj)) {
3054
- return function(...args) {
3055
- let template = h(...args);
3056
- let el = template.render();
3057
- Globals$1.objToEl.set(obj, el);
3058
- }.bind(obj);
3059
- }
3060
-
3061
3146
  // Normal path
3062
- else {
3147
+ if (!Globals$1.objToEl.has(obj)) {
3063
3148
  Globals$1.objToEl.set(obj, null);
3064
- obj[renderF](); // Calls the Special rebound render path above, when the render function calls r(this)
3149
+ obj[renderF](); // Calls the Special rebound render path above, when the render function calls h(this)
3065
3150
  let el = Globals$1.objToEl.get(obj);
3066
3151
  Globals$1.objToEl.delete(obj);
3067
3152
 
@@ -3069,7 +3154,7 @@ function h(htmlStrings=undefined, ...exprs) {
3069
3154
  if (typeof obj[name] === 'function')
3070
3155
  el[name] = obj[name].bind(el); // Make the "this" of functions be el.
3071
3156
  // TODO: But this doesn't work for passing an object with functions as a constructor arg via an attribute:
3072
- // <my-element arg=${{myFunc() { return this }}}
3157
+ // <my-element arg=${{myFunc() { return this }}}
3073
3158
  else
3074
3159
  el[name] = obj[name];
3075
3160
 
@@ -3085,13 +3170,145 @@ function h(htmlStrings=undefined, ...exprs) {
3085
3170
  }
3086
3171
  }
3087
3172
 
3088
- else
3089
- throw new Error('Unsupported arguments.')
3173
+ throw new Error('toEl() does not support argument of type: ' + (arg ? typeof arg : arg));
3174
+
3090
3175
  }
3091
3176
 
3177
+
3092
3178
  // Trick to prevent minifier from renaming this function.
3093
3179
  let renderF = 'render';
3094
3180
 
3181
+ /**
3182
+ * Convert strings to HTMLNodes.
3183
+ * Using h`...` as a tag will always create a Template.
3184
+ * Using h() as a function() will always create a DOM element.
3185
+ *
3186
+ * Features beyond what standard js tagged template strings do:
3187
+ * 1. r`` sub-expressions
3188
+ * 2. functions, nodes, and arrays of nodes as sub-expressions.
3189
+ * 3. html-escape all expressions by default, unless wrapped in h()
3190
+ * 4. event binding
3191
+ * 5. TODO: list more
3192
+ *
3193
+ * General rule:
3194
+ * If h() is a function with null or an HTMLElement as its first argument create a Node.
3195
+ * Otherwise create a template
3196
+ *
3197
+ * Currently supported:
3198
+ *
3199
+ * Create Tempataes
3200
+ * 1. h`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
3201
+ * 2. h('<b>Hello</b><u>Goodbye</u>'); // Create Template from string, that can later be used to create nodes.
3202
+ *
3203
+ * Add children to an element.
3204
+ * 3. h(el, h`<b>${'Hi'}</b>`, ?options)
3205
+ * 4. h(el, ?options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
3206
+ *
3207
+ * Create top-level element
3208
+ * 5. h()`Hello<b>${'World'}!</b>`
3209
+ *
3210
+ * 6. h(string, object, ...) // Used for JSX
3211
+ * @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
3212
+ * @param exprs {*[]|string|Template|Object}
3213
+ * @return {Node|HTMLElement|Template} */
3214
+ function h(htmlStrings=undefined, ...exprs) {
3215
+
3216
+ // 1. Tagged template
3217
+ if (Array.isArray(arguments[0])) {
3218
+ return new Template(arguments[0], exprs);
3219
+ }
3220
+
3221
+ // 2. String to template, or JSX factory form h(tag, props, ...children)
3222
+ else if (typeof arguments[0] === 'string' || arguments[0] instanceof String) {
3223
+ let tagOrHtml = arguments[0];
3224
+
3225
+ // 2a. JSX: h("tag", {props}, ...children)
3226
+ if (exprs.length && (typeof exprs[0] === 'object' || exprs[0] === null)) {
3227
+ let tag = tagOrHtml + '';
3228
+ let props = exprs[0] || {};
3229
+ let children = exprs.slice(1);
3230
+
3231
+ return Template.fromJsx(tag, props, children);
3232
+ }
3233
+
3234
+ // 2b. Plain html string => template
3235
+ else {
3236
+ let html = tagOrHtml;
3237
+ // If it starts with whitespace, trim both ends.
3238
+ // TODO: Also trim if it ends with whitespace?
3239
+ if (html.match(/^\s^</))
3240
+ html = html.trim();
3241
+ return new Template([html], []);
3242
+ }
3243
+ }
3244
+
3245
+ else if (arguments[0] instanceof HTMLElement || arguments[0] instanceof DocumentFragment) {
3246
+
3247
+ // 3. Render template to element.
3248
+ if (arguments[1] instanceof Template) {
3249
+
3250
+ /** @type Template */
3251
+ let template = arguments[1];
3252
+ let parent = arguments[0];
3253
+ let options = arguments[2]; // deprecated?
3254
+ template.render(parent, options);
3255
+ }
3256
+
3257
+ // 4. Render tagged template to element
3258
+ else {
3259
+ let parent = arguments[0], options = arguments[1];
3260
+
3261
+ // Remove shadowroot. TODO: This could mess up paths?
3262
+ if (parent.shadowRoot)
3263
+ parent.innerHTML = '';
3264
+
3265
+ // Return a tagged template function that applies the tagged template to parent.
3266
+ let taggedTemplate = (htmlStrings, ...exprs) => {
3267
+ Globals$1.rendered.add(parent);
3268
+ let template = new Template(htmlStrings, exprs);
3269
+ return template.render(parent, options);
3270
+ };
3271
+ return taggedTemplate;
3272
+ }
3273
+ }
3274
+
3275
+ // 5. Create a static element h()'<div></div>' (Deprecated?)
3276
+ else if (!arguments.length) {
3277
+ return (htmlStrings, ...exprs) => {
3278
+ let template = h(htmlStrings, ...exprs);
3279
+ return toEl(template); // Go to path 6.
3280
+ }
3281
+ }
3282
+
3283
+ // 6. Help toEl() with objects.
3284
+ // Special rebound render path, called by normal path.
3285
+ // Intercepts the main h(this)`...` function call inside render().
3286
+ // TODO: This path doesn't handle embeds like data-id="..."
3287
+ else if (typeof arguments[0] === 'object' && Globals$1.objToEl.has(arguments[0])) {
3288
+ let obj = arguments[0];
3289
+
3290
+ if (obj.constructor.name !== 'Object')
3291
+ throw new Error(`Solarate Web Component class ${obj.constructor?.name} must extend HTMLElement.`);
3292
+
3293
+ // Jsx with h(this, <jsx>)
3294
+ if (arguments[1] instanceof Template) {
3295
+ let template = arguments[1];
3296
+ let el = template.render();
3297
+ Globals$1.objToEl.set(obj, el);
3298
+ }
3299
+
3300
+ // h(this)`<div>...</div>
3301
+ else
3302
+ return function(...args) {
3303
+ let template = h(...args);
3304
+ let el = template.render();
3305
+ Globals$1.objToEl.set(obj, el);
3306
+ }.bind(obj);
3307
+ }
3308
+ else
3309
+ throw new Error('h() does not support argument of type: ' + (arguments[0] ? typeof arguments[0] : arguments[0]))
3310
+ }
3311
+
3095
3312
  /**
3096
3313
  * There are three ways to create an instance of a Solarite Component:
3097
3314
  * 1. new ComponentName(); // direct class instantiation
@@ -3265,7 +3482,7 @@ function createSolarite(extendsTag=null) {
3265
3482
 
3266
3483
  BaseClass = Globals$1.elementClasses[extendsTag];
3267
3484
  if (!BaseClass) { // TODO: Use Cache
3268
- BaseClass = document.createElement(extendsTag).constructor;
3485
+ BaseClass = Globals$1.doc.createElement(extendsTag).constructor;
3269
3486
  Globals$1.elementClasses[extendsTag] = BaseClass;
3270
3487
  }
3271
3488
  }
@@ -3326,7 +3543,7 @@ function createSolarite(extendsTag=null) {
3326
3543
  this.innerHTML = html;
3327
3544
  }
3328
3545
  else
3329
- this.modifications = r(this, html, options);
3546
+ this.modifications = h(this, html, options);
3330
3547
  }
3331
3548
  })*/
3332
3549
 
@@ -3376,11 +3593,14 @@ function createSolarite(extendsTag=null) {
3376
3593
  let define = 'define';
3377
3594
  let getName = 'getName';
3378
3595
 
3379
- /**
3380
- * Solarite JavasCript UI library.
3381
- * MIT License
3382
- * https://vorticode.github.io/solarite/
3383
- */
3596
+ /*
3597
+ ┏┓ ┓ •
3598
+ ┗┓┏┓┃┏┓┏┓┓╋▗▖
3599
+ ┗┛┗┛┗┗┻╹ ╹╹┗
3600
+ JavasCript UI library
3601
+ @license MIT
3602
+ @copyright Vorticode LLC
3603
+ https://vorticode.github.io/solarite/ */
3384
3604
 
3385
3605
  /**
3386
3606
  * TODO: The Proxy and the multiple base classes mess up 'instanceof Solarite'
@@ -3394,4 +3614,4 @@ const Solarite = new Proxy(createSolarite(), {
3394
3614
  //export {default as watch, renderWatched} from './watch.js'; // unfinished
3395
3615
 
3396
3616
  export default h;
3397
- export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, delve, getArg, h, h as r, setArgs };
3617
+ export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, getArg, h, h as r, setArgs, toEl };