solarite 0.1.0 → 0.1.1

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