solarite 0.1.1 → 0.2.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.
Files changed (45) hide show
  1. package/benchmarks/naive/Solarite.min.js +4 -0
  2. package/benchmarks/naive/index.html +14 -0
  3. package/benchmarks/naive/main.js +339 -0
  4. package/benchmarks/naive/package-lock.json +13 -0
  5. package/benchmarks/naive/package.json +23 -0
  6. package/benchmarks/readme.md +48 -0
  7. package/build/build.bat +2 -2
  8. package/build/build.js +1 -1
  9. package/dist/Solarite-debug.js +1344 -1532
  10. package/dist/Solarite.js +1298 -1334
  11. package/dist/Solarite.min.js +3 -3
  12. package/dist/udomdiff-license.txt +18 -0
  13. package/docs/index.md +440 -130
  14. package/docs/js/Playground.js +1 -1
  15. package/docs/js/codemirror/codemirror6.js +3683 -3206
  16. package/docs/js/codemirror/themeSolarIce.js +1 -1
  17. package/docs/js/documentation.js +1 -1
  18. package/docs/js/ui/CodeEditor.js +183 -45
  19. package/docs/js/ui/FlexResizer.js +14 -3
  20. package/docs/js/util/Errors.js +11 -0
  21. package/docs/media/documentation.css +6 -3
  22. package/index.html +449 -30
  23. package/package.json +1 -1
  24. package/readme.md +1 -1
  25. package/src/solarite/ExprPath.js +349 -151
  26. package/src/solarite/Globals.js +43 -1
  27. package/src/solarite/NodeGroup.js +275 -293
  28. package/src/solarite/Shell.js +26 -28
  29. package/src/solarite/Solarite.js +12 -0
  30. package/src/solarite/Template.js +55 -128
  31. package/src/solarite/Util.js +131 -7
  32. package/src/solarite/createSolarite.js +14 -19
  33. package/src/solarite/getArg.js +1 -1
  34. package/src/solarite/hash.js +18 -35
  35. package/src/solarite/r.js +127 -127
  36. package/src/solarite/watch3.js +18 -11
  37. package/src/{solarite → unused}/NodeGroupManager.js +81 -109
  38. package/src/{solarite → unused}/watch.js +1 -1
  39. package/src/{solarite → unused}/watch2.js +1 -2
  40. package/src/{solarite → util}/MultiValueMap.js +18 -11
  41. package/src/util/WeakArray.js +33 -0
  42. package/tests/Solarite.test.js +862 -167
  43. package/tests/index.html +4 -2
  44. package/tests/run.bat +1 -1
  45. package/deno.lock +0 -176
@@ -1,23 +1,12 @@
1
1
  import MultiValueMap from "./MultiValueMap.js";
2
2
  import NodeGroup from "./NodeGroup.js";
3
3
  import {getObjectHash} from "./hash.js";
4
- import {serializePath} from "./watch.js";
5
4
 
6
5
  import {assert} from "../util/Errors.js";
6
+ import Globals from "./Globals.js";
7
+ import Template from "./Template.js";
7
8
 
8
9
 
9
- /**
10
- * @typedef {Object} RenderOptions
11
- * @property {boolean=} styles - Indicates whether the Courage component is present.
12
- * @property {boolean=} scripts - Indicates whether the Power component is present.
13
- * @property {boolean=} ids *
14
- * @property {?boolean} render
15
- * Used only when options are given to a class super constructor inheriting from Solarite.
16
- * True to call render() immediately in super constructor.
17
- * False to automatically call render() at all.
18
- * Undefined (default) to call render() when added to the DOM, unless already rendered.
19
- */
20
-
21
10
 
22
11
  /**
23
12
  * Manage all the NodeGroups for a single WebComponent or root HTMLElement
@@ -25,7 +14,7 @@ import {assert} from "../util/Errors.js";
25
14
  * And each NodeGroup manages the one or more nodes created by the expression.
26
15
  *
27
16
  * An instance of this class exists for each element that r() renders to. */
28
- export default class NodeGroupManager {
17
+ class NodeGroupManager {
29
18
 
30
19
  /** @type {HTMLElement|DocumentFragment} */
31
20
  rootEl;
@@ -46,13 +35,17 @@ export default class NodeGroupManager {
46
35
  /**
47
36
  * A map from the html strings and exprs that created a node group, to the NodeGroup.
48
37
  * Also stores a map from just the html strings to the NodeGroup, so we can still find a similar match if the exprs changed.
49
- *
50
38
  * @type {MultiValueMap<string, (string|Template)[], NodeGroup>} */
51
39
  nodeGroupsAvailable = new MultiValueMap();
40
+
41
+ /**
42
+ * Save the NodeGroups that are returned by NodeGroupManager.get()
43
+ * Calling reset() at the end of render puts them all back into nodeGroupsAvailable.
44
+ * @type {NodeGroup[]} */
52
45
  nodeGroupsInUse = [];
53
46
 
54
47
 
55
- /** @type {RenderOptions} */
48
+ /** @type {RenderOptions} TODO: Where is this ever used? */
56
49
  options = {};
57
50
 
58
51
 
@@ -65,17 +58,19 @@ export default class NodeGroupManager {
65
58
  * @param rootEl {HTMLElement|DocumentFragment} If not specified, the first element of the html will be the rootEl. */
66
59
  constructor(rootEl=null) {
67
60
  this.rootEl = rootEl;
61
+ this.resetModifications();
68
62
 
69
63
  /*
70
64
  //#IFDEV
71
-
65
+
66
+ // Use MutationWatcher to check for illegal DOM modifications.
72
67
  function closestCustomElement(node) {
73
68
  do {
74
69
  if (node.tagName && node.tagName.includes('-'))
75
70
  return node;
76
71
  } while (node = node.parentNode);
77
72
  }
78
-
73
+
79
74
  // TODO: Only trigger if we modify nodes inside an ExprPath.
80
75
  // TODO: Enable this even when not in dev mode, because it's so useful for debugging?
81
76
  // But it modifies the top level prototypes.
@@ -147,9 +142,10 @@ export default class NodeGroupManager {
147
142
  if (!ng2.inUse) {
148
143
  ng2.inUse = true;
149
144
  let success = this.nodeGroupsAvailable.delete(ng2.exactKey, ng2);
150
- // assert(success);
145
+ assert(success);
146
+
151
147
  let success2 = this.nodeGroupsAvailable.delete(ng2.closeKey, ng2);
152
- // assert(success);
148
+ assert(success2);
153
149
  /*#IFDEV*/assert(success === success2)/*#ENDIF*/
154
150
 
155
151
  // console.log(getHtml(ng2))
@@ -163,8 +159,10 @@ export default class NodeGroupManager {
163
159
  // Recurse to mark all child NodeGroups as in-use.
164
160
  for (let path of ng.paths)
165
161
  for (let childNg of path.nodeGroups) {
166
- if (!childNg.inUse)
162
+ if (!childNg.inUse) {
167
163
  this.findAndDeleteExact(childNg.exactKey, false, childNg);
164
+ // this.findAndDeleteClose(childNg.closeKey, childNg.exactKey, false);
165
+ }
168
166
  childNg.inUse = true;
169
167
  }
170
168
 
@@ -189,6 +187,7 @@ export default class NodeGroupManager {
189
187
  if (ng) {
190
188
 
191
189
  // We matched on a new key, so delete the old exactKey.
190
+ /*#IFDEV*/assert(ng.exactKey);/*#ENDIF*/
192
191
  let exactNg = this.nodeGroupsAvailable.delete(ng.exactKey, ng);
193
192
 
194
193
  /*#IFDEV*/assert(exactNg);/*#ENDIF*/
@@ -203,10 +202,12 @@ export default class NodeGroupManager {
203
202
  while (ng2 = ng2?.parentPath?.parentNg) {
204
203
  if (!ng2.inUse) {
205
204
  ng2.inUse = true; // Might speed it up slightly?
205
+ /*#IFDEV*/assert(ng2.exactKey);/*#ENDIF*/
206
206
  let success = this.nodeGroupsAvailable.delete(ng2.exactKey, ng2);
207
207
  /*#IFDEV*/assert(success);/*#ENDIF*/
208
208
 
209
- // But it can still be a close match, so we don't use this code.
209
+ // But it can still be a close match, so we remove it there too.
210
+ /*#IFDEV*/assert(ng2.closeKey);/*#ENDIF*/
210
211
  success = this.nodeGroupsAvailable.delete(ng2.closeKey, ng2);
211
212
  /*#IFDEV*/assert(success);/*#ENDIF*/
212
213
 
@@ -244,59 +245,49 @@ export default class NodeGroupManager {
244
245
  * Get an existing or create a new NodeGroup that matches the template,
245
246
  * but don't reparent it if it's somewhere else.
246
247
  * @param template {Template}
247
- * @param exact {?boolean}
248
- * @param createForWatch Deprecated.
248
+ * @param exact {?boolean} If true, only get a NodeGroup if it matches both the template
249
+ * @param replaceMode {?boolean} If true, use the template to replace an existing element, instead of appending children to it.
249
250
  * @return {?NodeGroup} */
250
- getNodeGroup(template, exact=null, createForWatch=false) {
251
-
252
- let exactKey = getObjectHash(template)
251
+ getNodeGroup(template, exact=null, replaceMode=null, exactKey=null) {
252
+ exactKey = exactKey || getObjectHash(template);
253
253
 
254
- /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Looking for ${exact ? 'exact' : 'close'} match: ` + template.debug)/*#ENDIF*/
254
+ /*#IFDEV*/if(logEnabled) this.log(`Looking for ${exact ? 'exact' : 'close'} match: ` + template.debug)/*#ENDIF*/
255
255
 
256
256
  // 1. Try to find an exact match.
257
- let ng;
258
- if (exact === true) {
259
- ng = this.findAndDeleteExact(exactKey);
260
-
261
- if (!ng) {
262
- /*#IFDEV*/this.log(`Not found.`)/*#ENDIF*/
263
- return null;
264
- }
265
- /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Found exact: ` + ng.debug)/*#ENDIF*/
257
+ let ng = this.findAndDeleteExact(exactKey);
258
+ if (exact && !ng) {
259
+ /*#IFDEV*/this.log(`Not found.`)/*#ENDIF*/
260
+ return null;
266
261
  }
262
+ /*#IFDEV*/if (logEnabled) this.log(`Found exact: ` + ng.debug);/*#ENDIF*/
267
263
 
268
264
  // 2. Try to find a close match.
269
- else {
265
+ if (!ng) {
270
266
  // We don't need to delete the exact match bc it's already been deleted in the prev pass.
271
267
  let closeKey = template.getCloseKey();
272
- ng = createForWatch ? null : this.findAndDeleteClose(closeKey, exactKey);
268
+ ng = this.findAndDeleteClose(closeKey, exactKey);
273
269
 
274
270
  // 2. Update expression values if they've changed.
275
271
  if (ng) {
276
-
277
- // Temporary for debugging:
278
- if (window.debug && !window.ng)
279
- window.ng = ng;
280
-
281
- /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Found close: ` + closeKey + ' ' + ng.debug)/*#ENDIF*/
272
+ /*#IFDEV*/if (logEnabled) this.log(`Found close: ` + closeKey + ' ' + ng.debug);/*#ENDIF*/
282
273
  /*#IFDEV*/this.incrementLogDepth(1);/*#ENDIF*/
283
274
  /*#IFDEV*/ng.verify();/*#ENDIF*/
275
+
284
276
  ng.applyExprs(template.exprs);
285
277
 
286
- /*#IFDEV*/ng.verify()/*#ENDIF*/
278
+ /*#IFDEV*/ng.verify();/*#ENDIF*/
287
279
  /*#IFDEV*/this.incrementLogDepth(-1);/*#ENDIF*/
288
- /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Updated close to: ` + ng.debug)/*#ENDIF*/
280
+ /*#IFDEV*/if (logEnabled) this.log(`Updated close to: ` + ng.debug);/*#ENDIF*/
289
281
  }
290
282
 
291
283
  // 3. Or if not found, create a new NodeGroup
292
284
  else {
293
285
  /*#IFDEV*/this.incrementLogDepth(1);/*#ENDIF*/
294
- ng = new NodeGroup(template, this);
295
- /*#IFDEV*/this.incrementLogDepth(-1);/*#ENDIF*/
296
286
 
297
- //#IFDEV
298
- this.modifications.created.push(...ng.getNodes())
299
- //#ENDIF
287
+ ng = new NodeGroup(template, this, replaceMode, exactKey, closeKey);
288
+
289
+ /*#IFDEV*/this.incrementLogDepth(-1);/*#ENDIF*/
290
+ /*#IFDEV*/this.modifications.created.push(...ng.getNodes())/*#ENDIF*/
300
291
 
301
292
 
302
293
  // 4. Mark NodeGroup as being in-use.
@@ -305,12 +296,9 @@ export default class NodeGroupManager {
305
296
  // Perhaps also result could cache its last exprKey and then we'd use only one map?
306
297
  ng.exactKey = exactKey;
307
298
  ng.closeKey = closeKey;
308
- if (createForWatch)
309
- this.nodeGroupsAvailable.add(ng.exactKey, ng);
310
- else
311
- this.nodeGroupsInUse.push(ng)
299
+ this.nodeGroupsInUse.push(ng)
312
300
 
313
- /*#IFDEV*/if(NodeGroupManager.logEnabled) this.log(`Created new ` + ng.debug)/*#ENDIF*/
301
+ /*#IFDEV*/if (logEnabled) this.log(`Created new ` + ng.debug)/*#ENDIF*/
314
302
  }
315
303
  }
316
304
 
@@ -328,7 +316,13 @@ export default class NodeGroupManager {
328
316
  return ng;
329
317
  }
330
318
 
319
+ /**
320
+ * Move everything in nodeGroupsInUse to nodeGroupsAvailable.
321
+ * This is called after the NodeGroup has finished rendering. */
331
322
  reset() {
323
+
324
+ /*#IFDEV*/this.rootNg.verify();/*#ENDIF*/
325
+
332
326
  //this.changes = [];
333
327
  let available = this.nodeGroupsAvailable
334
328
  for (let ng of this.nodeGroupsInUse) {
@@ -343,61 +337,41 @@ export default class NodeGroupManager {
343
337
 
344
338
  /*#IFDEV*/this.log('----------------------')/*#ENDIF*/
345
339
  // TODO: free the memory from any nodeGroupsAvailable() after render is done, since they weren't used?
346
- }
347
340
 
348
341
 
349
- // deprecated
350
- //pathToLoopInfo = new MultiValueMap(); // uses a Set() for each value.
351
- clearSubscribers = false;
352
-
353
- //#IFDEV
354
-
355
- /**
356
- * @deprecated - part of watch.js (Watch v1)
357
- * One path may be used to loop in more than one place, so we use this to get every anchor from each loop.
358
- * @param path {Array}
359
- * @return {LoopInfo[]} A function that gets the loop anchor NodeGroup */
360
- getLoopInfo(path) {
361
- let serializedArrayPath = serializePath(path);
362
- return [...this.pathToLoopInfo.getAll(serializedArrayPath)]; // This is set inside forEach()
342
+ /*#IFDEV*/this.rootNg.verify();/*#ENDIF*/
363
343
  }
364
344
 
365
- //#ENDIF
366
-
367
-
368
- /**
369
- * @deprecated
370
- * Store the functions used to create items for each loop.
371
- * TODO: Can this be combined with pathToTemplates?
372
- * @type {MultiValueMap<string, Subscriber>} */
373
- pathToLoopInfo = new MultiValueMap();
374
-
375
- /**
376
- * Maps variable paths to the templates used to create NodeGroups
377
- * @type {MultiValueMap<string, Subscriber>} */
378
- subscribers = new MultiValueMap();
379
-
380
- clearSubscribersIfNeeded() {
381
- if (this.clearSubscribers) {
382
- this.pathToLoopInfo = new MultiValueMap();
383
- this.subscribers = new MultiValueMap();
384
- this.clearSubscribers = false;
385
- }
345
+ resetModifications() {
346
+ this.modifications = {
347
+ created: [],
348
+ updated: [],
349
+ moved: [],
350
+ deleted: []
351
+ };
386
352
  }
387
-
388
353
 
389
354
  /**
390
- * Get the NodeGroupManager for a Web Component.
391
- * @param rootEl {Solarite|HTMLElement}
355
+ * Get the NodeGroupManager for a Web Component, given either its root element or its Template.
356
+ * If given a Template, create a new NodeGroup.
357
+ * Using a Template is typically used when creating a standalone component.
358
+ * @param rootEl {Solarite|HTMLElement|Template}
392
359
  * @return {NodeGroupManager} */
393
360
  static get(rootEl=null) {
394
- if (!rootEl)
395
- return new NodeGroupManager();
361
+ let ngm;
362
+ if (rootEl instanceof Template) {
363
+ ngm = new NodeGroupManager();
364
+ ngm.rootNg = ngm.getNodeGroup(rootEl, false);
365
+ let el = ngm.rootNg.getRootNode();
366
+ Globals.nodeGroupManagers.set(el, ngm);
367
+ }
368
+ else {
396
369
 
397
- let ngm = nodeGroupManagers.get(rootEl);
398
- if (!ngm) {
399
- ngm = new NodeGroupManager(rootEl);
400
- nodeGroupManagers.set(rootEl, ngm);
370
+ ngm = Globals.nodeGroupManagers.get(rootEl);
371
+ if (!ngm) {
372
+ ngm = new NodeGroupManager(rootEl);
373
+ Globals.nodeGroupManagers.set(rootEl, ngm);
374
+ }
401
375
  }
402
376
 
403
377
  return ngm;
@@ -405,13 +379,13 @@ export default class NodeGroupManager {
405
379
 
406
380
 
407
381
  //#IFDEV
408
- static logEnabled = false;
382
+
409
383
  incrementLogDepth(level) {
410
384
  this.logDepth += level;
411
385
  }
412
386
  log(msg, level=0) {
413
387
  this.logDepth += level;
414
- if (NodeGroupManager.logEnabled) {
388
+ if (logEnabled) {
415
389
  let indent = ' '.repeat(this.logDepth);
416
390
  console.log(indent + msg);
417
391
  }
@@ -457,11 +431,9 @@ export default class NodeGroupManager {
457
431
 
458
432
  NodeGroupManager.pendingChildren = [];
459
433
 
460
- /**
461
- * Each Element that has Expr children has an associated NodeGroupManager here.
462
- * @type {WeakMap<HTMLElement, NodeGroupManager>} */
463
- let nodeGroupManagers = new WeakMap();
464
-
434
+ //#IFDEV
435
+ let logEnabled = false // Prevent rollup's static analyzer from removing this.
436
+ //#ENDIF
465
437
 
466
438
 
467
439
  export class LoopInfo {
@@ -5,7 +5,7 @@ import {assert} from "../util/Errors.js";
5
5
  import delve from "../util/delve.js";
6
6
  import {getObjectHash, getObjectId} from "./hash.js";
7
7
  import MultiValueMap from "./MultiValueMap.js";
8
- import NodeGroupManager, {LoopInfo} from "./NodeGroupManager.js";
8
+ //import NodeGroupManager, {LoopInfo} from "./NodeGroupManager.js";
9
9
  import Template from "./Template.js";
10
10
 
11
11
 
@@ -1,6 +1,6 @@
1
1
  import {assert} from "../util/Errors.js";
2
2
  import {getObjectId} from "./hash.js";
3
- import NodeGroupManager from "./NodeGroupManager.js";
3
+ //import NodeGroupManager from "./NodeGroupManager.js";
4
4
  import Template from "./Template.js";
5
5
  import {serializePath} from "./watch.js";
6
6
  import delve from "../util/delve.js";
@@ -113,7 +113,6 @@ class ProxyHandler {
113
113
  else if (prop === 'map' && Array.isArray(obj)) {
114
114
 
115
115
  let ngm = NodeGroupManager.get(this.root);
116
- ngm.clearSubscribersIfNeeded();
117
116
 
118
117
  return callback => {
119
118
  let loopInfo;
@@ -14,6 +14,12 @@ export default class MultiValueMap {
14
14
  set.add(value);
15
15
  }
16
16
 
17
+ isEmpty() {
18
+ for (let key in this.data)
19
+ return true;
20
+ return false;
21
+ }
22
+
17
23
  // Get all values for a key
18
24
  getAll(key) {
19
25
  return this.data[key] || [];
@@ -27,9 +33,9 @@ export default class MultiValueMap {
27
33
  delete(key, val=undefined) {
28
34
  // if (key === '["Html2",[[["Html3",["F1","A"]],["Html3",["F1","B"]]]]]')
29
35
  // debugger;
30
-
36
+
31
37
  let data = this.data;
32
- // The partialUpdate benchmark shows having this check first makes the function slightly faster.
38
+
33
39
  // if (!data.hasOwnProperty(key))
34
40
  // return undefined;
35
41
 
@@ -37,24 +43,25 @@ export default class MultiValueMap {
37
43
  let result;
38
44
  let set = data[key];
39
45
  if (!set) // slower than pre-check.
40
- return undefined;
46
+ return undefined;
41
47
 
42
- if (val !== undefined) {
43
- set.delete(val);
44
- result = val;
48
+ // Delete any value.
49
+ if (val === undefined) {
50
+ //result = set.values().next().value; // get first item from set.
51
+ [result] = set; // Does the same as above and seems to be about the same speed.
52
+ set.delete(result);
45
53
  }
46
54
 
47
- // Delete any value.
55
+ // Delete a specific value.
48
56
  else {
49
- result = set.values().next().value;
50
- // [result] = set; // Does the same as above. is about the same speed?
51
- set.delete(result);
57
+ set.delete(val);
58
+ result = val;
52
59
  }
53
60
 
54
61
  // TODO: Will this make it slower?
55
62
  if (set.size === 0)
56
63
  delete data[key];
57
-
64
+
58
65
  return result;
59
66
  }
60
67
 
@@ -0,0 +1,33 @@
1
+ export default class WeakArray {
2
+ constructor() {
3
+ this.items = [];
4
+ }
5
+
6
+ push(...items) {
7
+ for (let item of items) {
8
+ if (typeof item === 'object' && item)
9
+ this.items.push(new WeakRef(item));
10
+ else
11
+ throw new TypeError("Only objects can be added to a WeakArray");
12
+ }
13
+ }
14
+
15
+ get(index) {
16
+ const ref = this.items[index];
17
+ if (ref)
18
+ return ref.deref();
19
+ return undefined;
20
+ }
21
+
22
+ cleanup() {
23
+ this.items = this.items.filter(ref => ref.deref() !== undefined);
24
+ }
25
+
26
+ *[Symbol.iterator]() {
27
+ for (const ref of this.items) {
28
+ const value = ref.deref();
29
+ if (value !== undefined)
30
+ yield value;
31
+ }
32
+ }
33
+ }