solarite 0.2.3 → 0.2.4

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
@@ -76,10 +76,36 @@ var Util$1 = {
76
76
 
77
77
  return result;
78
78
  },
79
-
80
79
 
80
+ /**
81
+ * @param map {Map|WeakMap|Object}
82
+ * @param key
83
+ * @param value */
84
+ mapAdd(map, key, value) {
85
+ let isMap = map instanceof Map || map instanceof WeakMap;
86
+ let result = isMap ? map.get(key) : map[key];
87
+ if (!result) {
88
+ result = [value];
89
+ if (isMap)
90
+ map.set(key, result);
91
+ else
92
+ map[key] = result;
93
+ }
94
+ else
95
+ result.push(value);
96
+ },
81
97
 
82
- };
98
+ weakMemoize(obj, callback) {
99
+ let result = weakMemoizeInputs.get(obj);
100
+ if (!result) {
101
+ result = callback(obj);
102
+ weakMemoizeInputs.set(obj, result);
103
+ }
104
+ return result;
105
+ }
106
+ };
107
+
108
+ let weakMemoizeInputs = new WeakMap();
83
109
 
84
110
  /**
85
111
  * Follow a path into an object.
@@ -128,7 +154,7 @@ let delveDontCreate = {};
128
154
  /**
129
155
  * There are three ways to create an instance of a Solarite Component:
130
156
  * 1. new ComponentName(); // direct class instantiation
131
- * 2. this.html = r`<div><component-name></component-name></div>; // as a child of another Component.
157
+ * 2. this.html = r`<div><component-name></component-name></div>; // as a child of another RedComponent.
132
158
  * 3. <body><component-name></component-name></body> // in the Document html.
133
159
  *
134
160
  * When created via #3, Solarite has no way to pass attributes as arguments to the constructor. So to make
@@ -153,14 +179,15 @@ let delveDontCreate = {};
153
179
  * @param type {ArgType|function|*[]}
154
180
  * If an array, use the value if it's in the array, otherwise return undefined.
155
181
  * If it's a function, pass the value to the function and return the result.
156
- * @return {*} */
157
- function getArg(el, name, val=null, type=ArgType.String) {
182
+ * @param fallback {*} If the type can't be parsed as the given type, use this value.
183
+ * @return {*} Undefined if attribute isn't set. */
184
+ function getArg(el, name, val=undefined, type=ArgType.String, fallback=undefined) {
158
185
  let attrVal = el.getAttribute(name);
159
186
  if (attrVal !== null) // If attribute doesn't exist.
160
187
  val = attrVal;
161
188
 
162
189
  if (Array.isArray(type))
163
- return type.includes(val) ? val : undefined;
190
+ return type.includes(val) ? val : fallback;
164
191
 
165
192
  if (typeof type === 'function')
166
193
  return type(val);
@@ -168,15 +195,22 @@ function getArg(el, name, val=null, type=ArgType.String) {
168
195
  // If bool, it's true as long as it exists and its value isn't falsey.
169
196
  if (type===ArgType.Bool) {
170
197
  let lAttrVal = typeof val === 'string' ? val.toLowerCase() : val;
171
- return !['false', '0', false, 0, null, undefined].includes(lAttrVal);
198
+ if (['false', '0', false, 0, null, undefined, NaN].includes(lAttrVal))
199
+ return false;
200
+ if (['true', true].includes(lAttrVal) || parseFloat(lAttrVal) !== 0)
201
+ return true;
202
+ return fallback;
172
203
  }
173
204
 
174
205
  // Attribute doesn't exist
206
+ let result;
175
207
  switch (type) {
176
208
  case ArgType.Int:
177
- return parseInt(val);
209
+ result = parseInt(val);
210
+ return isNaN(result) ? fallback : result;
178
211
  case ArgType.Float:
179
- return parseFloat(val);
212
+ result = parseFloat(val);
213
+ return isNaN(result) ? fallback : result;
180
214
  case ArgType.String:
181
215
  return [undefined, null, false].includes(val) ? '' : val+'';
182
216
  case ArgType.JSON:
@@ -190,7 +224,9 @@ function getArg(el, name, val=null, type=ArgType.String) {
190
224
  } catch (e) {
191
225
  return val;
192
226
  }
193
- else return val;
227
+ else return fallback;
228
+
229
+ // type not provided
194
230
  default:
195
231
  return val;
196
232
  }
@@ -326,8 +362,9 @@ var Globals = {
326
362
  rendered: new WeakSet(),
327
363
 
328
364
  /**
329
- * Used by watch3 to see which expressions are being accessed. */
330
- currentExprPath: [],
365
+ * Used by watch3 to see which expressions are being accessed.
366
+ * @type {[]}*/
367
+ currentExprPath: null,
331
368
 
332
369
  /**
333
370
  * @type {Object<string, Class<Node>>} A map from built-in tag names to the constructors that create them. */
@@ -667,7 +704,10 @@ class MultiValueMap {
667
704
  return false;
668
705
  }
669
706
 
670
- // Get all values for a key
707
+ /**
708
+ * Get all values for a key.
709
+ * @param key {string}
710
+ * @returns {Set|*[]} */
671
711
  getAll(key) {
672
712
  return this.data[key] || [];
673
713
  }
@@ -676,7 +716,7 @@ class MultiValueMap {
676
716
  * Remove one value from a key, and return it.
677
717
  * @param key {string}
678
718
  * @param val If specified, make sure we delete this specific value, if a key exists more than once.
679
- * @returns {*} */
719
+ * @returns {*|undefined} The deleted item. */
680
720
  delete(key, val=undefined) {
681
721
  // if (key === '["Html2",[[["Html3",["F1","A"]],["Html3",["F1","B"]]]]]')
682
722
  // debugger;
@@ -712,6 +752,36 @@ class MultiValueMap {
712
752
  return result;
713
753
  }
714
754
 
755
+ /**
756
+ * Try to delete an item that matches the key and the isPreferred function.
757
+ * if not the latter, just delete any item that matches the key.
758
+ * @param key {string}
759
+ * @param isPreferred {function}
760
+ * @returns {*|undefined} The deleted item. */
761
+ deletePreferred(key, isPreferred) {
762
+ let result;
763
+ let data = this.data;
764
+ let set = data[key];
765
+ if (!set)
766
+ return undefined;
767
+
768
+ for (let val of set)
769
+ if (isPreferred(val)) {
770
+ set.delete(val);
771
+ result = val;
772
+ break;
773
+ }
774
+ if (!result) {
775
+ [result] = set;
776
+ set.delete(result);
777
+ }
778
+
779
+ if (set.size === 0)
780
+ delete data[key];
781
+
782
+ return result;
783
+ }
784
+
715
785
  hasValue(val) {
716
786
  let data = this.data;
717
787
  let names = [];
@@ -978,6 +1048,9 @@ class ExprPath {
978
1048
  nodeMarkerPath;
979
1049
 
980
1050
 
1051
+ /** @type {?function} */
1052
+ watchFunction
1053
+
981
1054
  /**
982
1055
  * @param nodeBefore {Node}
983
1056
  * @param nodeMarker {?Node}
@@ -1047,6 +1120,11 @@ class ExprPath {
1047
1120
  applyNodes(expr) {
1048
1121
  let path = this;
1049
1122
 
1123
+ // This can be done at the beginning or the end of this function.
1124
+ // If at the end, we may get rendering done faster.
1125
+ // But when at the beginning, it leaves all the nodes in-use so we can do a renderWatched().
1126
+ path.freeNodeGroups();
1127
+
1050
1128
 
1051
1129
 
1052
1130
  /** @type {(Node|NodeGroup|Expr)[]} */
@@ -1111,15 +1189,52 @@ class ExprPath {
1111
1189
  // Rearrange nodes.
1112
1190
  udomdiff(path.nodeMarker.parentNode, oldNodes, newNodes, path.nodeMarker);
1113
1191
 
1114
- Util.saveOrphans(oldNodeGroups, oldNodes);
1192
+ // TODO: Put this in a remove() function of NodeGroup.
1193
+ // Then only run it on the old nodeGroups that were actually removed.
1194
+ //Util.saveOrphans(oldNodeGroups, oldNodes);
1195
+
1196
+ for (let ng of oldNodeGroups)
1197
+ if (!ng.startNode.parentNode)
1198
+ ng.saveOrphans();
1115
1199
  }
1116
1200
 
1117
- // Must happen after second pass.
1118
- path.freeNodeGroups();
1119
1201
 
1120
1202
 
1121
1203
  }
1122
1204
 
1205
+ /**
1206
+ * Used by watch() for replacing individual loop items. */
1207
+ applyLoopItemUpdate(index, template) {
1208
+ // At this point none of the nodes being used will be in nodeGroupsFree.
1209
+ let oldNg = this.nodeGroups[index];
1210
+ this.nodeGroupsFree.add(oldNg.exactKey, oldNg);
1211
+ this.nodeGroupsFree.add(oldNg.closeKey, oldNg);
1212
+
1213
+ let ng = this.getNodeGroup(template, true);
1214
+ if (ng) {
1215
+ return; // It's an exactl match, so replace nothing.
1216
+ }
1217
+
1218
+
1219
+
1220
+ ng = this.getNodeGroup(template, false);
1221
+
1222
+ this.nodeGroups[index] = ng;
1223
+
1224
+ // Splice in the new nodes.
1225
+ for (let node of ng.getNodes()) {
1226
+ oldNg.startNode.parentNode.insertBefore(node, oldNg.startNode);
1227
+ }
1228
+
1229
+ if (oldNg !== ng) {
1230
+ for (let node of oldNg.getNodes())
1231
+ node.remove();
1232
+ oldNg.saveOrphans();
1233
+ }
1234
+
1235
+ // TODO: update or invalidate the nodes cache?
1236
+ this.nodesCache = null;
1237
+ }
1123
1238
 
1124
1239
 
1125
1240
  /**
@@ -1166,7 +1281,11 @@ class ExprPath {
1166
1281
  this.applyExact(subExpr, newNodes, secondPass);
1167
1282
 
1168
1283
  else if (typeof expr === 'function') {
1284
+ // TODO: One ExprPath can have multiple expr functions.
1285
+ // But if using it as a watch, it should only have one at the top level.
1286
+ // So maybe this is ok.
1169
1287
  Globals.currentExprPath = [this, expr]; // Used by watch3()
1288
+ this.watchFunction = expr; // TODO: Only do this if it's a top level function.
1170
1289
  let result = expr();
1171
1290
  Globals.currentExprPath = null;
1172
1291
 
@@ -1256,7 +1375,6 @@ class ExprPath {
1256
1375
  args = expr.slice(1);
1257
1376
  }
1258
1377
 
1259
- // Undocumented.
1260
1378
  // oninput=${[this, 'value']}
1261
1379
  else {
1262
1380
  func = setValue;
@@ -1268,20 +1386,27 @@ class ExprPath {
1268
1386
  else
1269
1387
  func = expr;
1270
1388
 
1389
+ this.bindEvent(node, root, eventName, eventName, func, args);
1390
+ }
1391
+
1392
+
1393
+ bindEvent(node, root, key, eventName, func, args, capture=false) {
1271
1394
  let nodeEvents = Globals.nodeEvents.get(node);
1272
1395
  if (!nodeEvents) {
1273
- nodeEvents = {[eventName]: new Array(3)};
1396
+ nodeEvents = {[key]: new Array(3)};
1274
1397
  Globals.nodeEvents.set(node, nodeEvents);
1275
1398
  }
1276
- let nodeEvent = nodeEvents[eventName];
1277
-
1399
+ let nodeEvent = nodeEvents[key];
1400
+ if (!nodeEvent)
1401
+ nodeEvents[key] = nodeEvent = new Array(3);
1278
1402
 
1279
1403
 
1280
1404
  // If function has changed, remove and rebind the event.
1281
1405
  if (nodeEvent[0] !== func) {
1406
+
1282
1407
  let [existing, existingBound, _] = nodeEvent;
1283
1408
  if (existing)
1284
- node.removeEventListener(eventName, existingBound);
1409
+ node.removeEventListener(eventName, existingBound, capture);
1285
1410
 
1286
1411
  let originalFunc = func;
1287
1412
 
@@ -1297,15 +1422,15 @@ class ExprPath {
1297
1422
  nodeEvent[0] = originalFunc;
1298
1423
  nodeEvent[1] = boundFunc;
1299
1424
 
1300
- node.addEventListener(eventName, boundFunc);
1425
+ node.addEventListener(eventName, boundFunc, capture);
1301
1426
 
1302
1427
  // TODO: classic event attribs?
1303
- //el[attr.name] = e => // e.g. el.onclick = ...
1304
- // (new Function('event', 'el', attr.value)).bind(this.manager.rootEl)(e, el) // put "event", "el", and "this" in scope for the event code.
1428
+ //el[attr.name] = e => // e.g. el.onclick = ... // put "event", "el", and "this" in scope for the event code.
1429
+ // (new Function('event', 'el', attr.value)).bind(this.manager.rootEl)(e, el)
1305
1430
  }
1306
1431
 
1307
1432
  // Otherwise just update the args to the function.
1308
- nodeEvents[eventName][2] = args;
1433
+ nodeEvents[key][2] = args;
1309
1434
  }
1310
1435
 
1311
1436
  applyValueAttrib(node, exprs, exprIndex) {
@@ -1323,9 +1448,14 @@ class ExprPath {
1323
1448
  else if ((this.attrName === 'value' || this.attrName === 'data-value') && Util.isPath(expr)) {
1324
1449
  let [obj, path] = [expr[0], expr.slice(1)];
1325
1450
  node.value = delve(obj, path);
1326
- node.addEventListener('input', () => {
1451
+ // TODO: We need to remove any old listeners, like in bindEventAttribute
1452
+
1453
+ let func = () => {
1327
1454
  delve(obj, path, Util.getInputValue(node));
1328
- }, true); // We use capture so we update the values before other events added by the user.
1455
+ };
1456
+
1457
+ // We use capture so we update the values before other events added by the user.
1458
+ this.bindEvent(node, path[0], 'value', 'input', func, [], true);
1329
1459
  }
1330
1460
 
1331
1461
  // Regular attribute
@@ -1502,8 +1632,9 @@ class ExprPath {
1502
1632
 
1503
1633
  let result;
1504
1634
 
1505
- if (exact) {
1506
- result = this.nodeGroupsFree.delete(template.getExactKey());
1635
+ // TODO: Would it be faster to maintain a separate list of detached nodegroups?
1636
+ if (exact) { // [below] parentElement will be null if the parent is a DocumentFragment
1637
+ result = this.nodeGroupsFree.deletePreferred(template.getExactKey(), ng=>ng.startNode.parentElement);
1507
1638
  if (result) // also delete the matching close key.
1508
1639
  this.nodeGroupsFree.delete(template.getCloseKey(), result);
1509
1640
  else
@@ -1514,7 +1645,7 @@ class ExprPath {
1514
1645
  // This is a match that has matching html, but different expressions applied.
1515
1646
  // We can then apply the expressions to make it an exact match.
1516
1647
  else {
1517
- result = this.nodeGroupsFree.delete(template.getCloseKey());
1648
+ result = this.nodeGroupsFree.deletePreferred(template.getCloseKey(), ng=>ng.startNode.parentElement);
1518
1649
  if (result) {
1519
1650
 
1520
1651
  this.nodeGroupsFree.delete(result.exactKey, result);
@@ -1557,12 +1688,18 @@ class ExprPath {
1557
1688
  * @type {MultiValueMap<key:string, value:NodeGroup>} */
1558
1689
  nodeGroupsFree = new MultiValueMap();
1559
1690
 
1691
+ nodeGroupsDetached = new MultiValueMap();
1692
+
1560
1693
 
1561
1694
  /**
1562
1695
  * Move everything from this.nodeGroupsInUse to this.nodeGroupsFree.
1563
1696
  * TODO: this could run as needed in getNodeGroup? */
1564
1697
  freeNodeGroups() {
1565
1698
  // old:
1699
+
1700
+ //this.nodeGroupsDetached = this.nodeGroupsFree;
1701
+ //this.nodeGroupsFree = new MultiValueMap();
1702
+
1566
1703
  let ngf = this.nodeGroupsFree;
1567
1704
  for (let ng of this.nodeGroupsInUse) {
1568
1705
  ngf.add(ng.exactKey, ng);
@@ -2274,6 +2411,16 @@ class NodeGroup {
2274
2411
  return this.rootNg;
2275
2412
  }
2276
2413
 
2414
+ /**
2415
+ * Requires the nodeCache to be present. */
2416
+ saveOrphans() {
2417
+
2418
+
2419
+ let fragment = document.createDocumentFragment();
2420
+ for (let node of this.getNodes())
2421
+ fragment.append(node);
2422
+ }
2423
+
2277
2424
 
2278
2425
  updatePaths(fragment, paths, offset) {
2279
2426
  // Update paths to point to the fragment.
@@ -2370,6 +2517,23 @@ class RootNodeGroup extends NodeGroup {
2370
2517
  * @type {HTMLElement} */
2371
2518
  root;
2372
2519
 
2520
+ /**
2521
+ * Store the expressions that use this watched variable,
2522
+ * along with the functions used to get their values.
2523
+ * @type {Object<field:string, Set<ExprPath>>} */
2524
+ watchedExprPaths = {};
2525
+
2526
+ /**
2527
+ * Map from arrays where .map is called and their callback functions.
2528
+ * TODO: One array might be called with two different map functions in different places!
2529
+ * @type {Map<Array, function>} */
2530
+ mapCallbacks = new Map();
2531
+
2532
+ /**
2533
+ *
2534
+ * @type {Map<ExprPath, boolean|Array>} */
2535
+ exprsToRender = new Map();
2536
+
2373
2537
  /**
2374
2538
  *
2375
2539
  * @param template
@@ -2388,6 +2552,7 @@ class RootNodeGroup extends NodeGroup {
2388
2552
  let offset = 0;
2389
2553
  let root = fragment; // TODO: Rename so it's not confused with this.root.
2390
2554
  if (el) {
2555
+ Globals.nodeGroups.set(el, this);
2391
2556
 
2392
2557
  // Save slot children
2393
2558
  let slotFragment;
@@ -2433,12 +2598,15 @@ class RootNodeGroup extends NodeGroup {
2433
2598
  }
2434
2599
 
2435
2600
  root = el;
2601
+
2436
2602
  this.startNode = el;
2437
2603
  this.endNode = el;
2438
2604
  }
2439
2605
  else {
2440
2606
  let singleEl = getSingleEl(fragment);
2441
2607
  this.root = singleEl || fragment; // We return the whole fragment when calling r() with a collection of nodes.
2608
+
2609
+ Globals.nodeGroups.set(this.root, this);
2442
2610
  if (singleEl) {
2443
2611
  root = singleEl;
2444
2612
  offset = 1;
@@ -2452,6 +2620,11 @@ class RootNodeGroup extends NodeGroup {
2452
2620
  // Apply exprs
2453
2621
  this.applyExprs(template.exprs);
2454
2622
  }
2623
+
2624
+ clearRenderWatched() {
2625
+ this.watchedExprPaths = {};
2626
+ this.mapCallbacks = new Map();
2627
+ }
2455
2628
  }
2456
2629
 
2457
2630
  function getSingleEl(fragment) {
@@ -2546,14 +2719,14 @@ class Template {
2546
2719
  if (standalone) {
2547
2720
  ng = new RootNodeGroup(this, null, options);
2548
2721
  el = ng.getRootNode();
2549
- Globals.nodeGroups.set(el, ng);
2722
+ //Globals.nodeGroups.set(el, ng);
2550
2723
  firstTime = true;
2551
2724
  }
2552
2725
  else {
2553
2726
  ng = Globals.nodeGroups.get(el);
2554
2727
  if (!ng) {
2555
2728
  ng = new RootNodeGroup(this, el, options);
2556
- Globals.nodeGroups.set(el, ng);
2729
+ //Globals.nodeGroups.set(el, ng);
2557
2730
  firstTime = true;
2558
2731
  }
2559
2732
  }
@@ -2563,8 +2736,10 @@ class Template {
2563
2736
  if (!firstTime) {
2564
2737
  if (this.html?.length === 1 && !this.html[0])
2565
2738
  el.innerHTML = ''; // Fast path for empty component.
2566
- else
2739
+ else {
2740
+ ng.clearRenderWatched();
2567
2741
  ng.applyExprs(this.exprs);
2742
+ }
2568
2743
  }
2569
2744
 
2570
2745
  return el;
@@ -2903,6 +3078,152 @@ function createSolarite(extendsTag=null) {
2903
3078
  }
2904
3079
  }
2905
3080
 
3081
+ /**
3082
+ * Trying to be able to automatically watch primitive values.
3083
+ * TODO:
3084
+ * 1. Have get() return Proxies for nested updates.
3085
+ * 2. Override .map() for loops to capture changes.
3086
+ */
3087
+
3088
+ let unusedArg = Symbol('unusedArg');
3089
+
3090
+ /**
3091
+ * Custom map function triggers the get() Proxy.
3092
+ * @param array {Array}
3093
+ * @param callback {function}
3094
+ * @returns {*[]} */
3095
+ function map(array, callback) {
3096
+ let result = [];
3097
+ for (let i=0; i<array.length; i++)
3098
+ result.push(callback(array[i], i, array));
3099
+ return result;
3100
+ }
3101
+
3102
+
3103
+ /**
3104
+ *
3105
+ * @param root {HTMLElement}
3106
+ * @param field {string}
3107
+ * @param value {string|Symbol} */
3108
+ function watch3(root, field, value=unusedArg) {
3109
+ // Store internal value used by get/set.
3110
+ if (value !== unusedArg)
3111
+ root[field] = value;
3112
+ else
3113
+ value = root[field];
3114
+
3115
+
3116
+ // use a single object for both defineProperty and new Proxy's handler.
3117
+ const handler = {
3118
+ get(obj, prop, receiver) {
3119
+
3120
+ let result = (obj === receiver && field === prop)
3121
+ ? value // top-level value.
3122
+ : Reflect.get(obj, prop, receiver); // avoid infinite recursion.
3123
+
3124
+ if (prop === 'map')
3125
+
3126
+ // Double function so the ExprPath calls it as a function,
3127
+ // instead of it being evaluated immediately when the Templat eis created.
3128
+ return (callback) => () => {
3129
+ let rootNg = Globals.nodeGroups.get(root);
3130
+ rootNg.mapCallbacks.set(obj, callback);
3131
+ return map(new Proxy(obj, handler), callback);
3132
+ }
3133
+
3134
+ // Track which ExprPath is using this variable.
3135
+ if (Globals.currentExprPath) {
3136
+ let [exprPath, exprFunction] = Globals.currentExprPath; // Set in ExprPath.applyExact()
3137
+
3138
+ let rootNg = Globals.nodeGroups.get(root);
3139
+
3140
+ // Init for field.
3141
+ rootNg.watchedExprPaths[field] = rootNg.watchedExprPaths[field] || new Set();
3142
+ rootNg.watchedExprPaths[field].add(exprPath);
3143
+ }
3144
+
3145
+ if (result && typeof result === 'object')
3146
+ return new Proxy(result, handler);
3147
+
3148
+ return result;
3149
+ },
3150
+
3151
+
3152
+ // TODO: Will fail for attribute w/ a value having multiple ExprPaths.
3153
+ // TODO: This won't update a component's expressions.
3154
+ set(obj, prop, val, receiver) {
3155
+
3156
+ // 1. Set the value.
3157
+ if (obj === receiver && field === prop)
3158
+ value = val; // top-level value.
3159
+ else // avoid infinite recursion.
3160
+ Reflect.set(obj, prop, val, receiver);
3161
+
3162
+ // 2. Add to the list of ExprPaths to re-render.
3163
+ let rootNg = Globals.nodeGroups.get(root);
3164
+ for (let exprPath of rootNg.watchedExprPaths[field]) {
3165
+
3166
+ // Update a single NodeGroup created by array.map()
3167
+ if (Array.isArray(obj) && parseInt(prop) == prop) {
3168
+ let exprsToRender = rootNg.exprsToRender.get(exprPath);
3169
+
3170
+ // If we're not re-rendering the whole thing.
3171
+ if (exprsToRender !== true)
3172
+ Util$1.mapAdd(rootNg.exprsToRender, exprPath, [obj, prop, val]);
3173
+ }
3174
+
3175
+ // Reapply the whole expression.
3176
+ else
3177
+ rootNg.exprsToRender.set(exprPath, true);
3178
+ }
3179
+ return true;
3180
+ }
3181
+ };
3182
+
3183
+ Object.defineProperty(root, field, {
3184
+ get: () => handler.get(root, field, root),
3185
+ set: (val) => handler.set(root, field, val, root)
3186
+ });
3187
+ }
3188
+
3189
+ /**
3190
+ * TODO: Rename so we have watch.add() and watch.render() ?
3191
+ * @param root
3192
+ * @returns {*[]} */
3193
+ function renderWatched(root) {
3194
+ let rootNg = Globals.nodeGroups.get(root);
3195
+ let modified = [];
3196
+
3197
+ for (let [exprPath, params] of rootNg.exprsToRender) {
3198
+
3199
+ // Reapply the whole expression.
3200
+ if (params === true) {
3201
+ exprPath.apply(exprPath.watchFunction);
3202
+
3203
+ // TODO: freeNodeGroups() could be skipped if applyExprs() never marked them as in-use.
3204
+ exprPath.freeNodeGroups();
3205
+
3206
+ modified.push(...exprPath.getNodes());
3207
+ }
3208
+
3209
+ // Update a single NodeGroup created by array.map()
3210
+ else {
3211
+ for (let row of params) {
3212
+ let [obj, prop, value] = row;
3213
+ let callback = rootNg.mapCallbacks.get(obj);
3214
+ let template = callback(value);
3215
+ exprPath.applyLoopItemUpdate(prop, template);
3216
+
3217
+ modified.push(...exprPath.nodeGroups[prop].getNodes());
3218
+ }
3219
+ }
3220
+ }
3221
+
3222
+ rootNg.exprsToRender = new Map(); // clear
3223
+
3224
+ return modified;
3225
+ }
3226
+
2906
3227
  /**
2907
3228
  * Solarite JavasCript UI library.
2908
3229
  * MIT License
@@ -2918,9 +3239,6 @@ let Solarite = new Proxy(createSolarite(), {
2918
3239
  }
2919
3240
  });
2920
3241
  let getInputValue = Util.getInputValue;
2921
-
2922
- //Experimental:
2923
- //export {forEach, watchGet, watchSet} from './watch.js' // old, unfinished
2924
- //export {watch} from './watch2.js'; // unfinished
3242
+ // unfinished
2925
3243
 
2926
- export { ArgType, Globals, Solarite, Template, delve, getArg, getInputValue, r };
3244
+ export { ArgType, Globals, Solarite, Template, delve, getArg, getInputValue, r, renderWatched, watch3 as watch };