solarite 0.3.2 → 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
 
@@ -995,18 +1017,17 @@ class ExprPath {
995
1017
  ng.removeAndSaveOrphans();
996
1018
 
997
1019
  // Instantiate components created within ${...} expressions.
998
- // 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.
999
1021
  for (let el of newNodes) {
1000
- if (el instanceof HTMLElement) {
1022
+ if (el?.nodeType === 1) { // HTMLElement
1001
1023
  if (el.hasAttribute('solarite-placeholder'))
1002
- this.parentNg.instantiateComponent(el);
1024
+ this.parentNg.handleComponent(el, null, true);
1003
1025
  for (let child of el.querySelectorAll('[solarite-placeholder]'))
1004
- this.parentNg.instantiateComponent(child);
1026
+ this.parentNg.handleComponent(child, null, true);
1005
1027
  }
1006
1028
  }
1007
1029
  }
1008
1030
 
1009
-
1010
1031
 
1011
1032
  }
1012
1033
 
@@ -1114,7 +1135,7 @@ class ExprPath {
1114
1135
  }
1115
1136
 
1116
1137
  // String/Number/Date/Boolean
1117
- else if (!(expr instanceof Template) && !(expr instanceof Node)){
1138
+ else if (!(expr instanceof Template) && !(expr?.nodeType)){
1118
1139
  // Convert expression to a string.
1119
1140
  if (expr === undefined || expr === false || expr === null) // Util.isFalsy() inlined
1120
1141
  expr = '';
@@ -1124,7 +1145,9 @@ class ExprPath {
1124
1145
  // Get the same Template for the same string each time.
1125
1146
  // let template = Globals.stringTemplates[expr];
1126
1147
  // if (!template) {
1148
+
1127
1149
  let template = new Template([expr], []);
1150
+ template.isText = true;
1128
1151
  // Globals.stringTemplates[expr] = template;
1129
1152
  //}
1130
1153
 
@@ -1149,12 +1172,44 @@ class ExprPath {
1149
1172
 
1150
1173
  if (expr instanceof Template) {
1151
1174
  let ng = this.getNodeGroup(expr, true);
1175
+
1152
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
+ }
1153
1205
 
1154
- // TODO: Track ranges of changed nodes and only pass those to udomdiff?
1155
- // But will that break the swap benchmark?
1156
- 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
+
1157
1210
  this.nodeGroups.push(ng);
1211
+
1212
+ return ng;
1158
1213
  }
1159
1214
 
1160
1215
  // If expression, mark it to be evaluated later in ExprPath.apply() to find partial match.
@@ -1166,10 +1221,10 @@ class ExprPath {
1166
1221
  }
1167
1222
 
1168
1223
  // Node(s) created by an expression.
1169
- else if (expr instanceof Node) {
1224
+ else if (expr?.nodeType) {
1170
1225
 
1171
1226
  // DocumentFragment created by an expression.
1172
- if (expr instanceof DocumentFragment)
1227
+ if (expr?.nodeType === 11) // DocumentFragment
1173
1228
  newNodes.push(...expr.childNodes);
1174
1229
  else
1175
1230
  newNodes.push(expr);
@@ -1323,7 +1378,7 @@ class ExprPath {
1323
1378
  }
1324
1379
 
1325
1380
  /**
1326
- * Handle values, including two-way binding.
1381
+ * Set the value of an attribute. This can be for any attribute, not just attributes named "value".
1327
1382
  * @param node
1328
1383
  * @param exprs */
1329
1384
  // TODO: node is always this.nodeMarker?
@@ -1353,12 +1408,21 @@ class ExprPath {
1353
1408
  }
1354
1409
  else {
1355
1410
  // TODO: should we remove isFalsy, since these are always props?
1356
- let strValue = Util.isFalsy(value) ? '' : value;
1411
+ const strValue = Util.isFalsy(value) ? '' : value;
1412
+
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 {
1357
1420
 
1358
- // If we don't have this condition, when we call render(), the browser will scroll to the currently
1359
- // selected item in a <select> and mess up manually scrolling to a different value.
1360
- if (strValue !== node[this.attrName])
1361
- node[this.attrName] = strValue;
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
+ }
1362
1426
  }
1363
1427
 
1364
1428
  // TODO: We need to remove any old listeners, like in bindEventAttribute.
@@ -1378,16 +1442,8 @@ class ExprPath {
1378
1442
 
1379
1443
  // Regular attribute
1380
1444
  else {
1381
- // TODO: Cache this on ExprPath.isProp when Shell creates the props. Have ExprPath.clone() copy .isProp
1382
- // Or make it a new PathType.
1383
- //if (this.attrName === 'disabled')
1384
- // debugger;
1385
-
1386
- // hasOwnProperty() checks only the object, not the parents
1387
- // this.attrName in node checks the node and the parents.
1388
- // This version checks the html element it extends from, to see if has a setter set:
1389
- // Object.getOwnPropertyDescriptor(Object.getPrototypeOf(node), this.attrName)?.set
1390
- //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?
1391
1447
  let isProp = this.isHtmlProperty;
1392
1448
  if (isProp === undefined)
1393
1449
  isProp = this.isHtmlProperty = Util.isHtmlProp(node, this.attrName);
@@ -1397,7 +1453,7 @@ class ExprPath {
1397
1453
  if (!multiple) {
1398
1454
  Globals$1.currentExprPath = this; // Used by watch()
1399
1455
  if (typeof expr === 'function') {
1400
- 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
1401
1457
  return
1402
1458
  }
1403
1459
  this.watchFunction = expr; // The function that gets the expression, used for renderWatched()
@@ -1455,6 +1511,13 @@ class ExprPath {
1455
1511
  // since we also prohibit expressions that are a child of textarea.
1456
1512
  if (isProp)
1457
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
+
1458
1521
  // TODO: Putting an 'else' here would be more performant
1459
1522
  node.setAttribute(this.attrName, joinedValue);
1460
1523
  }
@@ -1485,6 +1548,7 @@ class ExprPath {
1485
1548
  nodeBefore = childNodes[this.nodeBeforeIndex];
1486
1549
 
1487
1550
  let result = new ExprPath(nodeBefore, nodeMarker, this.type, this.attrName, this.attrValue);
1551
+ result.isComponent = this.isComponent;
1488
1552
 
1489
1553
 
1490
1554
 
@@ -1493,9 +1557,7 @@ class ExprPath {
1493
1557
 
1494
1558
  /**
1495
1559
  * Clear the nodeCache of this ExprPath, as well as all parent and child ExprPaths that
1496
- * share the same DOM parent node.
1497
- *
1498
- * TODO: Is recursive clearing ever necessary? */
1560
+ * share the same DOM parent node. */
1499
1561
  clearNodesCache() {
1500
1562
  let path = this;
1501
1563
 
@@ -1508,9 +1570,6 @@ class ExprPath {
1508
1570
  // If stuck in an infinite loop here, the problem is likely due to Template hash colisions.
1509
1571
  // Which cause one path to be the descendant of itself, creating a cycle.
1510
1572
  }
1511
-
1512
- // Commented out on Sep 30, 2024 b/c it was making the benchmark never finish when adding 10k rows.
1513
- //clearChildNodeCache(this);
1514
1573
  }
1515
1574
 
1516
1575
 
@@ -1552,7 +1611,7 @@ class ExprPath {
1552
1611
  // result2.push(...ng.getNodes())
1553
1612
  // return result2;
1554
1613
 
1555
- if (this.type === ExprPathType.AttribValue || this.type === ExprPathType.AttribMultiple || this.type === ExprPathType.ComponentAttribValue) {
1614
+ if (this.type === ExprPathType.AttribValue || this.type === ExprPathType.AttribMultiple) {
1556
1615
  return [this.nodeMarker];
1557
1616
  }
1558
1617
 
@@ -1610,11 +1669,13 @@ class ExprPath {
1610
1669
  result = collection.deleteAny(template.getExactKey());
1611
1670
  }
1612
1671
 
1613
- if (result) // also delete the matching close key.
1672
+ if (result) {// also delete the matching close key.
1614
1673
  collection.deleteSpecific(template.getCloseKey(), result);
1615
- else {
1616
- return null;
1674
+
1675
+ //result.applyExprs(template.exprs);
1617
1676
  }
1677
+ else
1678
+ return null;
1618
1679
  }
1619
1680
 
1620
1681
  // Find a close match.
@@ -1648,12 +1709,6 @@ class ExprPath {
1648
1709
  return result;
1649
1710
  }
1650
1711
 
1651
- isComponent() {
1652
- // Events won't have type===Component.
1653
- // TODO: Have a special flag for components instead of it being on the type?
1654
- return this.type === ExprPathType.ComponentAttribValue || (this.attrName && this.nodeMarker.tagName && this.nodeMarker.tagName.includes('-'));
1655
- }
1656
-
1657
1712
  /**
1658
1713
  * TODO: Rename this to nodeGroupsInUse, nodeGroupsAvialableAttached and nodeGroupsAvailableDetached?
1659
1714
  * Nodes that have been used during the current render().
@@ -1710,7 +1765,7 @@ class ExprPath {
1710
1765
  /** @enum {int} */
1711
1766
  const ExprPathType = {
1712
1767
  /** Child of a node */
1713
- Content: 1,
1768
+ Content: 1, // TODO: Rename to Nodes
1714
1769
 
1715
1770
  /** One or more whole attributes */
1716
1771
  AttribMultiple: 2,
@@ -1718,14 +1773,11 @@ const ExprPathType = {
1718
1773
  /** Value of an attribute. */
1719
1774
  AttribValue: 3,
1720
1775
 
1721
- /** Value of an attribute being passed to a component. */
1722
- ComponentAttribValue: 4,
1723
-
1724
1776
  /** Expressions inside Html comments. */
1725
- Comment: 5,
1777
+ Comment: 4,
1726
1778
 
1727
1779
  /** Value of an attribute. */
1728
- Event: 6,
1780
+ Event: 5,
1729
1781
  };
1730
1782
 
1731
1783
 
@@ -1891,7 +1943,7 @@ class Shell {
1891
1943
 
1892
1944
 
1893
1945
  if (html.length === 1 && !html[0].match(/[<&]/)) {
1894
- this.fragment = document.createTextNode(html[0]);
1946
+ this.fragment = Globals$1.doc.createTextNode(html[0]);
1895
1947
  return;
1896
1948
  }
1897
1949
 
@@ -1899,18 +1951,18 @@ class Shell {
1899
1951
  // 1. Add placeholders
1900
1952
  let joinedHtml = Shell.addPlaceholders(html);
1901
1953
 
1902
- 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.
1903
1955
  if (joinedHtml)
1904
1956
  template.innerHTML = joinedHtml;
1905
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.
1906
- template.content.append(document.createTextNode(''));
1958
+ template.content.append(Globals$1.doc.createTextNode(''));
1907
1959
  this.fragment = template.content;
1908
1960
 
1909
1961
  // 2. Find placeholders
1910
1962
  let node;
1911
1963
  let toRemove = [];
1912
1964
  let placeholdersUsed = 0;
1913
- 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);
1914
1966
  while (node = walker.nextNode()) {
1915
1967
 
1916
1968
  // Remove previous after each iteration, so paths will still be calculated correctly.
@@ -1946,10 +1998,13 @@ class Shell {
1946
1998
  // Replace comment placeholders
1947
1999
  else if (node.nodeType === 8 && node.nodeValue === '!✨!') {
1948
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
+
1949
2004
  // Get or create nodeBefore.
1950
2005
  let nodeBefore = node.previousSibling; // Can be the same as another Path's nodeMarker.
1951
2006
  if (!nodeBefore) {
1952
- nodeBefore = document.createComment('ExprPath:'+this.paths.length);
2007
+ nodeBefore = Globals$1.doc.createComment('ExprPath:'+this.paths.length);
1953
2008
  node.parentNode.insertBefore(nodeBefore, node);
1954
2009
  }
1955
2010
 
@@ -1974,9 +2029,9 @@ class Shell {
1974
2029
  placeholdersUsed ++;
1975
2030
  }
1976
2031
 
2032
+ // Comments become text nodes when inside textareas.
1977
2033
  else if (node.nodeType === 3 && node.parentNode?.tagName === 'TEXTAREA' && node.textContent.includes('<!--!✨!-->'))
1978
2034
  throw new Error(`Textarea can't have expressions inside them. Use <textarea value="\${...}"> instead.`);
1979
-
1980
2035
 
1981
2036
 
1982
2037
  // Sometimes users will comment out a block of html code that has expressions.
@@ -2000,7 +2055,7 @@ class Shell {
2000
2055
 
2001
2056
  let placeholders = [];
2002
2057
  for (let i = 0; i<parts.length; i++) {
2003
- let current = document.createTextNode(parts[i]);
2058
+ let current = Globals$1.doc.createTextNode(parts[i]);
2004
2059
  node.parentNode.insertBefore(current, node);
2005
2060
  if (i > 0)
2006
2061
  placeholders.push(current);
@@ -2039,9 +2094,9 @@ class Shell {
2039
2094
  path.nodeMarkerPath = getNodePath(path.nodeMarker);
2040
2095
 
2041
2096
  // Cache so we don't have to calculate this later inside NodeGroup.applyExprs()
2042
- if (path.type === ExprPathType.AttribValue && path.nodeMarker.nodeType === 1 &&
2097
+ if ((path.type === ExprPathType.AttribValue || path.type === ExprPathType.Event) && path.nodeMarker.nodeType === 1 &&
2043
2098
  (path.nodeMarker.tagName.includes('-') || path.nodeMarker.hasAttribute('is'))) {
2044
- path.type = ExprPathType.ComponentAttribValue;
2099
+ path.isComponent = true;
2045
2100
  }
2046
2101
  }
2047
2102
 
@@ -2207,30 +2262,39 @@ class NodeGroup {
2207
2262
  * @type {?Map<HTMLStyleElement, string>} */
2208
2263
  styles;
2209
2264
 
2265
+ dynamicComponents = new Set();
2266
+ staticComponents = [];
2267
+
2268
+ /** @type {Template} */
2269
+ template;
2270
+
2210
2271
 
2211
2272
  /**
2212
2273
  * Create an "instantiated" NodeGroup from a Template and add it to an element.
2213
2274
  * @param template {Template} Create it from the html strings and expressions in this template.
2214
2275
  * @param parentPath {?ExprPath} */
2215
2276
  constructor(template, parentPath=null) {
2277
+ this.rootNg = parentPath?.parentNg?.rootNg || this;
2278
+ this.parentPath = parentPath;
2279
+
2216
2280
  if (!(this instanceof RootNodeGroup)) {
2217
2281
 
2218
- let [fragment, shell] = this.init(template, parentPath);
2282
+ let [fragment, shell] = this.populateFromTemplate(template);
2219
2283
 
2220
2284
  if (fragment && template.exprs.length) {
2221
2285
  this.updatePaths(fragment, shell.paths);
2222
2286
 
2223
2287
  // Static web components can sometimes have children created via expressions.
2224
2288
  // But calling applyExprs() will mess up the shell's path to them.
2225
- // So we find them first, then call activateStaticComponents() after their children have been created.
2226
- 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);
2227
2291
 
2228
2292
  this.activateEmbeds(fragment, shell);
2229
2293
 
2230
2294
  // Apply exprs
2231
2295
  this.applyExprs(template.exprs);
2232
2296
 
2233
- this.instantiateStaticComponents(staticComponents);
2297
+ this.instantiateStaticComponents(this.staticComponents);
2234
2298
  }
2235
2299
  else if (shell)
2236
2300
  this.activateEmbeds(fragment, shell);
@@ -2241,47 +2305,33 @@ class NodeGroup {
2241
2305
  * Common init shared by RootNodeGroup and NodeGroup constructors.
2242
2306
  * But in a separate function because they need to do this at a different step.
2243
2307
  * @param template {Template} Create it from the html strings and expressions in this template.
2244
- * @param parentPath {?ExprPath}
2245
- * @param exactKey {?string} Optional, if already calculated.
2246
- * @param closeKey {?string}
2247
- * @returns {[DocumentFragment, Shell]} */
2248
- init(template, parentPath=null, exactKey=null, closeKey=null) {
2249
- this.exactKey = exactKey || template.getExactKey();
2250
- this.closeKey = closeKey || template.getCloseKey();
2251
-
2252
- this.parentPath = parentPath;
2253
- this.rootNg = parentPath?.parentNg?.rootNg || this;
2254
-
2308
+ * @returns {[DocumentFragment, Shell]} The Shell created from the template,a nd the fragment cloned from the Shell.*/
2309
+ populateFromTemplate(template) {
2255
2310
 
2256
-
2257
- /** @type {Template} */
2258
2311
  this.template = template;
2259
-
2260
- // new! Is this needed?
2261
- template.nodeGroup = this;
2262
-
2263
- // Get a cached version of the parsed and instantiated html, and ExprPaths.
2312
+ this.exactKey = template.getExactKey();
2313
+ this.closeKey = template.getCloseKey();
2264
2314
 
2265
2315
  // If it's just a text node, skip a bunch of unnecessary steps.
2266
- if (!(this instanceof RootNodeGroup) && !template.exprs.length && !template.html[0].includes('<')) {
2267
- //let doc = this.rootNg.startNode?.ownerDocument || document;
2268
- let textNode = document.createTextNode(template.html[0]);
2269
-
2316
+ if (template.isText) {
2317
+ let textNode = Globals$1.doc.createTextNode(template.html[0]);
2270
2318
  this.startNode = this.endNode = textNode;
2271
2319
  return [];
2272
2320
  }
2321
+
2322
+ // Get a cached version of the parsed and instantiated html, and ExprPaths:
2273
2323
  else {
2274
2324
  let shell = Shell.get(template.html);
2275
2325
  let fragment = shell.fragment.cloneNode(true);
2276
2326
 
2277
- if (fragment instanceof DocumentFragment) {
2327
+ if (fragment?.nodeType === 11) { // DocumentFragment
2278
2328
  let childNodes = fragment.childNodes;
2279
2329
  this.startNode = childNodes[0];
2280
2330
  this.endNode = childNodes[childNodes.length - 1];
2281
2331
  }
2282
- else {
2332
+ else
2283
2333
  this.startNode = this.endNode = fragment;
2284
- }
2334
+
2285
2335
  return [fragment, shell];
2286
2336
  }
2287
2337
  }
@@ -2319,6 +2369,7 @@ class NodeGroup {
2319
2369
  exprIndex--;
2320
2370
  }
2321
2371
 
2372
+
2322
2373
  // TODO: Need to end and restart this block when going from one component to the next?
2323
2374
  // Think of having two adjacent components.
2324
2375
  // But the dynamicAttribsAdjacet test already passes.
@@ -2327,11 +2378,11 @@ class NodeGroup {
2327
2378
  // 1. Instantiate it if it hasn't already been, sending all expr's to its constructor.
2328
2379
  // 2. Otherwise send them to its render function.
2329
2380
  // Components with no expressions as attributes are instead activated in activateEmbeds().
2330
- if (path.nodeMarker !== this.rootNg.root && path.isComponent()) {
2381
+ if (path.nodeMarker !== this.rootNg.root && path.isComponent) {
2331
2382
 
2332
- if (!nextPath || !nextPath.isComponent() || nextPath.nodeMarker !== path.nodeMarker)
2383
+ if (!nextPath || !nextPath.isComponent || nextPath.nodeMarker !== path.nodeMarker)
2333
2384
  lastComponentPathIndex = i;
2334
- let isFirstComponentPath = !prevPath || !prevPath.isComponent() || prevPath.nodeMarker !== path.nodeMarker;
2385
+ let isFirstComponentPath = !prevPath || !prevPath.isComponent || prevPath.nodeMarker !== path.nodeMarker;
2335
2386
 
2336
2387
  if (isFirstComponentPath) {
2337
2388
 
@@ -2341,7 +2392,7 @@ class NodeGroup {
2341
2392
  componentProps[attrName] = pathExprs[j].length > 1 ? pathExprs[j].join('') : pathExprs[j][0];
2342
2393
  }
2343
2394
 
2344
- this.applyComponentExprs(path.nodeMarker, componentProps);
2395
+ this.handleComponent(path.nodeMarker, componentProps, true);
2345
2396
 
2346
2397
  // Set attributes on component.
2347
2398
  for (let j=i; j<=lastComponentPathIndex; j++)
@@ -2360,7 +2411,10 @@ class NodeGroup {
2360
2411
  // TODO: Only do this if we have ExprPaths within styles?
2361
2412
  this.updateStyles();
2362
2413
 
2363
-
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.
2364
2418
 
2365
2419
  // Invalidate the nodes cache because we just changed it.
2366
2420
  this.nodesCache = null;
@@ -2374,50 +2428,39 @@ class NodeGroup {
2374
2428
  }
2375
2429
 
2376
2430
  /**
2377
- * Create a nested Component or call render with the new props.
2378
- * @param el {Solarite:HTMLElement}
2379
- * @param props {Object} */
2380
- applyComponentExprs(el, props) {
2381
-
2382
- // TODO: Does a hash of this already exist somewhere?
2383
- // Perhaps if Components were treated as child NodeGroups, which would need to be the child of an ExprPath,
2384
- // then we could re-use the hash and logic from NodeManager?
2385
- let newHash = getObjectHash(props);
2386
-
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) {
2387
2437
  let isPreHtmlElement = el.hasAttribute('solarite-placeholder');
2388
2438
  let isPreIsElement = el.hasAttribute('_is');
2389
-
2390
-
2391
- // Instantiate a placeholder.
2439
+ let attribs, children;
2392
2440
  if (isPreHtmlElement || isPreIsElement)
2393
- el = this.instantiateComponent(el, isPreHtmlElement, props);
2394
-
2395
- // Call render() with the same params that would've been passed to the constructor.
2396
- // We do this even if the arguments haven't changed, so we can let the child component
2397
- // compare the arguments and then decide for itself whether it wants to re-render.
2398
- else if (el.render) {
2399
- //let oldHash = Globals.componentArgsHash.get(el);
2400
- //if (oldHash !== newHash) { // Only if not changed.
2401
- let args = {};
2441
+ [el, attribs, children] = this.instantiateComponent(el, isPreHtmlElement, props);
2442
+ if (doRender && el.render) {
2443
+ if (!attribs) {
2444
+ attribs = Util.attribsToObject(el);
2402
2445
  for (let name in props || {})
2403
- args[Util.dashesToCamel(name)] = props[name];
2404
- el.render(args); // Pass new values of props to render so it can decide how it wants to respond.
2405
- //}
2446
+ attribs[Util.dashesToCamel(name)] = props[name];
2447
+ children = el.childNodes;
2448
+ }
2449
+ el.render(attribs, children);
2406
2450
  }
2407
-
2408
- Globals$1.componentArgsHash.set(el, newHash);
2451
+ return el;
2409
2452
  }
2410
-
2453
+
2411
2454
  /**
2412
2455
  * We swap the placeholder element for the real element so we can pass its dynamic attributes
2413
2456
  * to its constructor.
2457
+ * This is only called by handleComponent()
2458
+ * This does not call render()
2414
2459
  *
2415
- * The logic of this function is complex and could use cleaning up.
2416
- *
2417
- * @param el
2460
+ * @param el {HTMLElement}
2418
2461
  * @param isPreHtmlElement {?boolean} True if the element's tag name ends with -solarite-placeholder
2419
2462
  * @param props {Object} Attributes with dynamic values.
2420
- * @return {HTMLElement} */
2463
+ * @return {[HTMLElement, attribs:Object, children:Node[]]}} */
2421
2464
  instantiateComponent(el, isPreHtmlElement=undefined, props=undefined) {
2422
2465
  if (isPreHtmlElement === undefined)
2423
2466
  isPreHtmlElement = !el.hasAttribute('_is');
@@ -2432,19 +2475,12 @@ class NodeGroup {
2432
2475
  if (!Constructor)
2433
2476
  throw new Error(`The custom tag name ${tagName} is not registered.`)
2434
2477
 
2435
- let attribs = {};
2478
+ // Pass other attribs to constructor, since otherwise they're not yet set on the element,
2479
+ // and the constructor would otherwise have no way to see them.
2480
+ let attribs = Util.attribsToObject(el, 'solarite-placeholder');
2436
2481
  for (let name in props || {})
2437
2482
  attribs[Util.dashesToCamel(name)] = props[name];
2438
2483
 
2439
- // Pass other attribs to constructor, since otherwise they're not yet set on the element,
2440
- // and the constructor would otherwise have no way to see them.
2441
- if (el.attributes.length) {
2442
- for (let attrib of el.attributes) {
2443
- let attribName = Util.dashesToCamel(attrib.name);
2444
- if (!attribs.hasOwnProperty(attribName) && attribName !== 'solarite-placeholder')
2445
- attribs[attribName] = attrib.value;
2446
- }
2447
- }
2448
2484
 
2449
2485
  // Create the web component.
2450
2486
  // Get the children that aren't Solarite's comment placeholders.
@@ -2475,7 +2511,7 @@ class NodeGroup {
2475
2511
  if (this.endNode === el)
2476
2512
  this.endNode = newEl;
2477
2513
 
2478
-
2514
+ // This is used only if inheriting from the Solarite class.
2479
2515
  // applyComponentExprs() is called because we're rendering.
2480
2516
  // So we want to render the sub-component also.
2481
2517
  if (newEl.renderFirstTime)
@@ -2499,7 +2535,7 @@ class NodeGroup {
2499
2535
  newEl.setAttribute(name, val);
2500
2536
  }
2501
2537
 
2502
- return newEl;
2538
+ return [newEl, attribs, children];
2503
2539
  }
2504
2540
 
2505
2541
  /**
@@ -2546,18 +2582,21 @@ class NodeGroup {
2546
2582
  * Requires the nodeCache to be present. */
2547
2583
  removeAndSaveOrphans() {
2548
2584
 
2549
- let fragment = document.createDocumentFragment();
2585
+ let fragment = Globals$1.doc.createDocumentFragment();
2550
2586
  for (let node of this.getNodes())
2551
2587
  fragment.append(node);
2552
2588
  }
2553
2589
 
2554
2590
 
2555
- updatePaths(fragment, paths, offset) {
2556
- // Update paths to point to the fragment.
2591
+ /**
2592
+ * @param fragment {DocumentFragment}
2593
+ * @param paths
2594
+ * @param startingPathDepth {int} */
2595
+ updatePaths(fragment, paths, startingPathDepth) {
2557
2596
  let pathLength = paths.length;
2558
2597
  this.paths.length = pathLength;
2559
2598
  for (let i=0; i<pathLength; i++) {
2560
- let path = paths[i].clone(fragment, offset);
2599
+ let path = paths[i].clone(fragment, startingPathDepth);
2561
2600
  path.parentNg = this;
2562
2601
  this.paths[i] = path;
2563
2602
  }
@@ -2574,7 +2613,7 @@ class NodeGroup {
2574
2613
 
2575
2614
 
2576
2615
 
2577
- findStaticComponents(root, shell, pathOffset=0) {
2616
+ findStaticComponents(root, shell, startingPathDepth=0) {
2578
2617
  let result = [];
2579
2618
 
2580
2619
  // static components. These are WebComponents that do not have any constructor arguments that are expressions.
@@ -2582,8 +2621,8 @@ class NodeGroup {
2582
2621
  // Maybe someday these two paths will be merged?
2583
2622
  // Must happen before ids because instantiateComponent will replace the element.
2584
2623
  for (let path of shell.staticComponents) {
2585
- if (pathOffset)
2586
- path = path.slice(0, -pathOffset);
2624
+ if (startingPathDepth)
2625
+ path = path.slice(0, -startingPathDepth);
2587
2626
  let el = resolveNodePath(root, path);
2588
2627
 
2589
2628
  // Shell doesn't know if a web component is the pseudoRoot so we have to detect it here.
@@ -2595,8 +2634,9 @@ class NodeGroup {
2595
2634
  }
2596
2635
 
2597
2636
  instantiateStaticComponents(staticComponents) {
2598
- for (let el of staticComponents)
2599
- 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);
2600
2640
  }
2601
2641
 
2602
2642
  /**
@@ -2617,7 +2657,7 @@ class NodeGroup {
2617
2657
  let el = resolveNodePath(root, path);
2618
2658
  Util.bindId(rootEl, el);
2619
2659
  }
2620
- }
2660
+ }
2621
2661
 
2622
2662
  // styles
2623
2663
  if (options?.styles !== false) {
@@ -2647,9 +2687,8 @@ class NodeGroup {
2647
2687
  }
2648
2688
  }
2649
2689
  }
2650
- }
2651
-
2652
-
2690
+ }
2691
+
2653
2692
  class RootNodeGroup extends NodeGroup {
2654
2693
 
2655
2694
  /**
@@ -2663,18 +2702,18 @@ class RootNodeGroup extends NodeGroup {
2663
2702
  exprsToRender = new Map();
2664
2703
 
2665
2704
  /**
2666
- *
2667
- * @param template
2668
- * @param el
2669
- * @param options {?object}
2670
- */
2705
+ * @param template {Template}
2706
+ * @param el {?HTMLElement} Optional, pre-existing htmlElement tat will be the root.
2707
+ * @param options {?object} */
2671
2708
  constructor(template, el, options) {
2672
2709
  super(template);
2673
2710
 
2674
2711
  this.options = options;
2675
2712
 
2676
- this.rootNg = this;
2677
- let [fragment, shell] = this.init(template);
2713
+ let [fragment, shell] = this.populateFromTemplate(template);
2714
+
2715
+ let startingPathDepth = 0;
2716
+
2678
2717
 
2679
2718
  if (fragment instanceof Text) {
2680
2719
 
@@ -2685,25 +2724,25 @@ class RootNodeGroup extends NodeGroup {
2685
2724
  el.append(fragment);
2686
2725
  this.root = el;
2687
2726
  }
2727
+ else
2728
+ throw new Error('Cannot create a standalone text node');
2688
2729
  Globals$1.nodeGroups.set(this.root, this);
2689
2730
  }
2731
+
2732
+
2690
2733
  else {
2691
2734
 
2692
2735
  // If adding NodeGroup to an element.
2693
- let offset = 0;
2694
- let root = fragment; // TODO: Rename so it's not confused with this.root.
2695
2736
  if (el) {
2696
- Globals$1.nodeGroups.set(el, this);
2737
+ this.root = el;
2697
2738
 
2698
2739
  // Save slot children
2699
2740
  let slotChildren;
2700
2741
  if (el.childNodes.length) {
2701
- slotChildren = document.createDocumentFragment();
2742
+ slotChildren = Globals$1.doc.createDocumentFragment();
2702
2743
  slotChildren.append(...el.childNodes);
2703
2744
  }
2704
2745
 
2705
- this.root = el;
2706
-
2707
2746
  // If el should replace the root node of the fragment.
2708
2747
  if (isReplaceEl(fragment, el)) {
2709
2748
  el.append(...fragment.children[0].childNodes);
@@ -2714,8 +2753,10 @@ class RootNodeGroup extends NodeGroup {
2714
2753
  el.setAttribute(attrib.name, attrib.value);
2715
2754
 
2716
2755
  // Go one level deeper into all of shell's paths.
2717
- offset = 1;
2718
- } else {
2756
+ startingPathDepth = 1;
2757
+ }
2758
+
2759
+ else {
2719
2760
  let isEmpty = fragment.childNodes.length === 1 && fragment.childNodes[0].nodeType === 3 && fragment.childNodes[0].textContent === '';
2720
2761
  if (!isEmpty)
2721
2762
  el.append(...fragment.childNodes);
@@ -2743,35 +2784,35 @@ class RootNodeGroup extends NodeGroup {
2743
2784
  el.append(slotChildren);
2744
2785
  }
2745
2786
 
2746
- root = el;
2747
-
2748
2787
  this.startNode = el;
2749
2788
  this.endNode = el;
2750
- } else {
2789
+ }
2790
+
2791
+ // Instantiate as a standalone element.
2792
+ else {
2751
2793
  let singleEl = getSingleEl(fragment);
2752
- 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.
2753
2795
 
2754
- Globals$1.nodeGroups.set(this.root, this);
2755
- if (singleEl) {
2756
- root = singleEl;
2757
- offset = 1;
2758
- }
2796
+ if (singleEl)
2797
+ startingPathDepth = 1;
2759
2798
  }
2760
-
2761
- this.updatePaths(root, shell.paths, offset);
2799
+ Globals$1.nodeGroups.set(this.root, this);
2800
+ this.updatePaths(this.root, shell.paths, startingPathDepth);
2762
2801
 
2763
2802
  // Static web components can sometimes have children created via expressions.
2764
2803
  // But calling applyExprs() will mess up the shell's path to them.
2765
2804
  // So we find them first, then call activateStaticComponents() after their children have been created.
2766
- let staticComponents = this.findStaticComponents(root, shell, offset);
2805
+ this.staticComponents = this.findStaticComponents(this.root, shell, startingPathDepth);
2767
2806
 
2768
- this.activateEmbeds(root, shell, offset);
2807
+ this.activateEmbeds(this.root, shell, startingPathDepth);
2769
2808
 
2770
2809
  // Apply exprs
2771
2810
  this.applyExprs(template.exprs);
2772
2811
 
2773
- this.instantiateStaticComponents(staticComponents);
2812
+ this.instantiateStaticComponents(this.staticComponents);
2774
2813
  }
2814
+
2815
+
2775
2816
  }
2776
2817
  }
2777
2818
 
@@ -2813,8 +2854,7 @@ class Template {
2813
2854
  /** @type {Array} Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
2814
2855
  hashedFields;
2815
2856
 
2816
- /** @type {NodeGroup} */
2817
- nodeGroup;
2857
+ isText;
2818
2858
 
2819
2859
  /**
2820
2860
  *
@@ -2910,169 +2950,203 @@ class Template {
2910
2950
 
2911
2951
  return this.closeKey;
2912
2952
  }
2913
- }
2914
2953
 
2954
+ /**
2955
+ * @param tag {string}
2956
+ * @param props {?Record<string, any>}
2957
+ * @param children
2958
+ * @returns {Template} */
2959
+ static fromJsx(tag, props, children) {
2915
2960
 
2916
- /**
2917
- * @typedef {Object} RenderOptions
2918
- * @property {boolean=} styles - Replace :host in style tags to scope them locally.
2919
- * @property {boolean=} scripts - Execute script tags.
2920
- * @property {boolean=} ids - Create references to elements with id or data-id attributes.
2921
- * @property {?boolean} render - Deprecated.
2922
- * Used only when options are given to a class super constructor inheriting from Solarite.
2923
- * True to call render() immediately in super constructor.
2924
- * False to automatically call render() at all.
2925
- * Undefined (default) to call render() when added to the DOM, unless already rendered.
2926
- */
2927
-
2928
- /**
2929
- * Convert strings to HTMLNodes.
2930
- * Using h`...` as a tag will always create a Template.
2931
- * Using h() as a function() will always create a DOM element.
2932
- *
2933
- * Features beyond what standard js tagged template strings do:
2934
- * 1. r`` sub-expressions
2935
- * 2. functions, nodes, and arrays of nodes as sub-expressions.
2936
- * 3. html-escape all expressions by default, unless wrapped in r()
2937
- * 4. event binding
2938
- * 5. TODO: list more
2939
- *
2940
- * Currently supported:
2941
- * 1. h(el, options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
2942
- * 2. h(el, template, ?options) // Render the Template created by #1 to element.
2943
- *
2944
- * 3. h`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
2945
- *
2946
- * 4. h('Hello'); // Create single text node.
2947
- * 5. h('<b>Hello</b>'); // Create single HTMLElement
2948
- * 6. h('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
2949
- * 7. h()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which
2950
- * // includes properly handling nested components and r`` sub-expressions.
2951
- * 8. h(template) // Render Template created by #1.
2952
- *
2953
- * 9. h({render(){...}}) // Pass an object with a render method, and optionally other props/methods.
2954
- * 10. h(string, object, ...) // JSX TODO
2955
- * @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
2956
- * @param exprs {*[]|string|Template|Object}
2957
- * @return {Node|HTMLElement|Template} */
2958
- function h(htmlStrings=undefined, ...exprs) {
2961
+ // HTML void elements that must not have closing tags
2962
+ const isVoid = selfClosingTags.has(tag.toLowerCase());
2959
2963
 
2960
- if (htmlStrings === undefined && !exprs.length && arguments.length)
2961
- 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 = [];
2962
2967
 
2963
- // TODO: Make this a more flat if/else and call other functions for the logic.
2964
- if (htmlStrings instanceof Node) {
2965
- let parent = htmlStrings, template = exprs[0];
2968
+ // Opening tag
2969
+ let open = `<${tag}`;
2966
2970
 
2967
- // 1
2968
- if (!(exprs[0] instanceof Template)) {
2969
- if (parent.shadowRoot)
2970
- 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];
2971
2975
 
2972
- 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
+ }
2973
2982
 
2974
- // Return a tagged template function that applies the tagged themplate to parent.
2975
- let taggedTemplate = (htmlStrings, ...exprs) => {
2976
- Globals$1.rendered.add(parent);
2977
- let template = new Template(htmlStrings, exprs);
2978
- return template.render(parent, options);
2979
- };
2980
- 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
+ }
2981
2999
  }
2982
3000
 
2983
- // 2. Render template created by #4 to element.
2984
- else { // instanceof Template
2985
- let options = exprs[1];
2986
- template.render(parent, options);
2987
-
2988
- // Append on the first go.
2989
- if (!parent.childNodes.length && this) {
2990
- // TODO: Is this ever executed?
2991
- debugger;
2992
- 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 === '"' ? '">' : '>');
2993
3011
  }
3012
+
3013
+ for (let child of children)
3014
+ addChild(child, htmlStrings, templateExprs);
2994
3015
  }
2995
- }
2996
3016
 
2997
- // 3. Path if used as a template tag.
2998
- else if (Array.isArray(htmlStrings)) {
2999
- 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);
3000
3037
  }
3038
+ }
3001
3039
 
3002
- else if (typeof htmlStrings === 'string' || htmlStrings instanceof String) {
3003
- // 10. JSX
3004
- if (typeof exprs[0] === 'object') {
3005
- exprs[0] || {};
3006
- exprs.slice(1);
3007
3040
 
3008
- let templateHtmlStrings = [];
3009
- let templateExprs = [];
3041
+ const selfClosingTags = new Set(['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr']);
3010
3042
 
3011
- // TODO How to know which children are static html and which are expression placeholders?
3012
- // Perhaps we have to treat every text child as a string?
3013
3043
 
3014
- assert(templateHtmlStrings.length === templateExprs.length+1);
3015
- 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
+ }
3016
3070
  }
3017
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
+ };
3018
3086
 
3019
- // If it starts with a string, trim both ends.
3020
- // TODO: Also trim if it ends with whitespace?
3021
- if (htmlStrings.match(/^\s^</))
3022
- 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();
3023
3118
 
3024
3119
  // We create a new one each time because otherwise
3025
3120
  // the returned fragment will have its content replaced by a subsequent call.
3026
- let templateEl = document.createElement('template');
3027
- templateEl.innerHTML = htmlStrings;
3121
+ let templateEl = Globals$1.doc.createElement('template');
3122
+ templateEl.innerHTML = html;
3028
3123
 
3029
- // 4+5. Return Node if there's one child.
3124
+ // 1+2. Return Node if there's one child.
3030
3125
  let relevantNodes = Util.trimEmptyNodes(templateEl.content.childNodes);
3031
3126
  if (relevantNodes.length === 1)
3032
3127
  return relevantNodes[0];
3033
3128
 
3034
- // 6. Otherwise return DocumentFragment.
3129
+ // 3. Otherwise return DocumentFragment.
3035
3130
  return templateEl.content;
3036
3131
  }
3037
3132
 
3038
- // 7. Create a static element
3039
- else if (htmlStrings === undefined) {
3040
- return (htmlStrings, ...exprs) => {
3041
- //Globals.rendered.add(parent)
3042
- let template = h(htmlStrings, ...exprs);
3043
- return template.render();
3044
- }
3045
- }
3046
-
3047
- // 8.
3048
- else if (htmlStrings instanceof Template) {
3049
- return htmlStrings.render();
3133
+ // 4.
3134
+ if (arg instanceof Template) {
3135
+ return arg.render();
3050
3136
  }
3051
3137
 
3052
-
3053
- // 9. Create dynamic element with render() function.
3138
+ // 5. Create dynamic element from an object with a render() function.
3054
3139
  // TODO: This path doesn't handle embeds like data-id="..."
3055
- else if (typeof htmlStrings === 'object') {
3056
- let obj = htmlStrings;
3140
+ else if (arg && typeof arg === 'object') {
3141
+ let obj = arg;
3057
3142
 
3058
- if (obj.constructor.name !== 'Object')
3143
+ if (obj.constructor.name !== 'Object')
3059
3144
  throw new Error(`Solarate Web Component class ${obj.constructor?.name} must extend HTMLElement.`);
3060
3145
 
3061
-
3062
- // Special rebound render path, called by normal path.
3063
- // Intercepts the main r`...` function call inside render().
3064
- if (Globals$1.objToEl.has(obj)) {
3065
- return function(...args) {
3066
- let template = h(...args);
3067
- let el = template.render();
3068
- Globals$1.objToEl.set(obj, el);
3069
- }.bind(obj);
3070
- }
3071
-
3072
3146
  // Normal path
3073
- else {
3147
+ if (!Globals$1.objToEl.has(obj)) {
3074
3148
  Globals$1.objToEl.set(obj, null);
3075
- 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)
3076
3150
  let el = Globals$1.objToEl.get(obj);
3077
3151
  Globals$1.objToEl.delete(obj);
3078
3152
 
@@ -3080,7 +3154,7 @@ function h(htmlStrings=undefined, ...exprs) {
3080
3154
  if (typeof obj[name] === 'function')
3081
3155
  el[name] = obj[name].bind(el); // Make the "this" of functions be el.
3082
3156
  // TODO: But this doesn't work for passing an object with functions as a constructor arg via an attribute:
3083
- // <my-element arg=${{myFunc() { return this }}}
3157
+ // <my-element arg=${{myFunc() { return this }}}
3084
3158
  else
3085
3159
  el[name] = obj[name];
3086
3160
 
@@ -3096,13 +3170,145 @@ function h(htmlStrings=undefined, ...exprs) {
3096
3170
  }
3097
3171
  }
3098
3172
 
3099
- else
3100
- throw new Error('Unsupported arguments.')
3173
+ throw new Error('toEl() does not support argument of type: ' + (arg ? typeof arg : arg));
3174
+
3101
3175
  }
3102
3176
 
3177
+
3103
3178
  // Trick to prevent minifier from renaming this function.
3104
3179
  let renderF = 'render';
3105
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
+
3106
3312
  /**
3107
3313
  * There are three ways to create an instance of a Solarite Component:
3108
3314
  * 1. new ComponentName(); // direct class instantiation
@@ -3276,7 +3482,7 @@ function createSolarite(extendsTag=null) {
3276
3482
 
3277
3483
  BaseClass = Globals$1.elementClasses[extendsTag];
3278
3484
  if (!BaseClass) { // TODO: Use Cache
3279
- BaseClass = document.createElement(extendsTag).constructor;
3485
+ BaseClass = Globals$1.doc.createElement(extendsTag).constructor;
3280
3486
  Globals$1.elementClasses[extendsTag] = BaseClass;
3281
3487
  }
3282
3488
  }
@@ -3337,7 +3543,7 @@ function createSolarite(extendsTag=null) {
3337
3543
  this.innerHTML = html;
3338
3544
  }
3339
3545
  else
3340
- this.modifications = r(this, html, options);
3546
+ this.modifications = h(this, html, options);
3341
3547
  }
3342
3548
  })*/
3343
3549
 
@@ -3387,11 +3593,14 @@ function createSolarite(extendsTag=null) {
3387
3593
  let define = 'define';
3388
3594
  let getName = 'getName';
3389
3595
 
3390
- /**
3391
- * Solarite JavasCript UI library.
3392
- * MIT License
3393
- * https://vorticode.github.io/solarite/
3394
- */
3596
+ /*
3597
+ ┏┓ ┓ •
3598
+ ┗┓┏┓┃┏┓┏┓┓╋▗▖
3599
+ ┗┛┗┛┗┗┻╹ ╹╹┗
3600
+ JavasCript UI library
3601
+ @license MIT
3602
+ @copyright Vorticode LLC
3603
+ https://vorticode.github.io/solarite/ */
3395
3604
 
3396
3605
  /**
3397
3606
  * TODO: The Proxy and the multiple base classes mess up 'instanceof Solarite'
@@ -3405,4 +3614,4 @@ const Solarite = new Proxy(createSolarite(), {
3405
3614
  //export {default as watch, renderWatched} from './watch.js'; // unfinished
3406
3615
 
3407
3616
  export default h;
3408
- 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 };