solarite 0.1.0 → 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 (48) 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 +3 -2
  8. package/build/build.js +1 -1
  9. package/dist/Solarite-debug.js +1941 -2791
  10. package/dist/Solarite.js +1697 -2511
  11. package/dist/Solarite.min.js +3 -3
  12. package/dist/udomdiff-license.txt +18 -0
  13. package/docs/index.md +695 -235
  14. package/docs/js/Playground.js +1 -1
  15. package/docs/js/codemirror/codemirror6.js +3683 -3206
  16. package/docs/js/codemirror/themeSolarIce.js +2 -2
  17. package/docs/js/documentation.js +2 -2
  18. package/docs/js/ui/CodeEditor.js +183 -45
  19. package/docs/js/ui/FlexResizer.js +15 -5
  20. package/docs/js/util/Errors.js +11 -0
  21. package/docs/media/documentation.css +6 -3
  22. package/index.html +462 -26
  23. package/package.json +1 -1
  24. package/readme.md +11 -1
  25. package/src/solarite/ExprPath.js +422 -135
  26. package/src/solarite/Globals.js +53 -0
  27. package/src/solarite/NodeGroup.js +300 -388
  28. package/src/solarite/Shell.js +31 -33
  29. package/src/solarite/Solarite.js +17 -2
  30. package/src/solarite/Template.js +75 -25
  31. package/src/solarite/Util.js +131 -7
  32. package/src/solarite/createSolarite.js +32 -25
  33. package/src/solarite/getArg.js +12 -12
  34. package/src/solarite/hash.js +18 -35
  35. package/src/solarite/r.js +128 -118
  36. package/src/solarite/watch3.js +98 -0
  37. package/src/{solarite → unused}/NodeGroupManager.js +86 -224
  38. package/src/unused/onConnect.js +79 -0
  39. package/src/{solarite → unused}/watch.js +2 -2
  40. package/src/{solarite → unused}/watch2.js +4 -4
  41. package/src/{solarite → util}/MultiValueMap.js +23 -12
  42. package/src/util/WeakArray.js +33 -0
  43. package/tests/Solarite.test.js +1108 -166
  44. package/tests/Testimony.js +274 -67
  45. package/tests/index.html +6 -35
  46. package/tests/run.bat +2 -0
  47. package/tests/NodeGroup.test.js +0 -115
  48. package/tests/Shell.test.js +0 -75
@@ -4,7 +4,7 @@
4
4
  /**
5
5
  * There are three ways to create an instance of a Solarite Component:
6
6
  * 1. new ComponentName(); // direct class instantiation
7
- * 2. this.html = r`<div><component-name></component-name></div>; // as a child of another RedComponent.
7
+ * 2. this.html = r`<div><component-name></component-name></div>; // as a child of another Component.
8
8
  * 3. <body><component-name></component-name></body> // in the Document html.
9
9
  *
10
10
  * When created via #3, Solarite has no way to pass attributes as arguments to the constructor. So to make
@@ -54,19 +54,19 @@ export function getArg(el, name, val=null, type=ArgType.String) {
54
54
  case ArgType.Float:
55
55
  return parseFloat(val);
56
56
  case ArgType.String:
57
- return val || '';
57
+ return [undefined, null, false].includes(val) ? '' : val+'';
58
58
  case ArgType.JSON:
59
- try {
60
- return JSON.parse(val);
61
- } catch (e) {
62
- return val;
63
- }
64
59
  case ArgType.Eval:
65
- try {
66
- return eval(`(${val})`);
67
- } catch (e) {
68
- return val;
69
- }
60
+ if (typeof val === 'string' && val.length)
61
+ try {
62
+ if (type === ArgType.JSON)
63
+ return JSON.parse(val);
64
+ else
65
+ return eval(`(${val})`);
66
+ } catch (e) {
67
+ return val;
68
+ }
69
+ else return val;
70
70
  default:
71
71
  return val;
72
72
  }
@@ -3,32 +3,14 @@ let objectIds = new WeakMap();
3
3
 
4
4
  /**
5
5
  * @param obj {Object|string|Node}
6
- * @param prefix
7
6
  * @returns {string} */
8
- export function getObjectId(obj, prefix=null) {
9
-
10
-
11
-
12
- //#IFDEV
13
- // Slower but useful for debugging:
14
- if (!prefix) {
15
- if (Array.isArray(obj))
16
- prefix = 'Array';
17
- else if (typeof obj === 'function')
18
- prefix = 'Func';
19
- else if (typeof obj === 'object')
20
- prefix = 'Obj'
21
- }
22
- //#ENDIF
23
-
24
- prefix = prefix || '~\f';
25
-
7
+ export function getObjectId(obj) {
26
8
  // if (typeof obj === 'function')
27
- // return obj.toString();
9
+ // return obj.toString(); // This fails to detect when a function's bound variables changes.
28
10
 
29
11
  let result = objectIds.get(obj);
30
12
  if (!result) { // convert to string, store in result, then add 1 to lastObjectId.
31
- result = prefix+(lastObjectId++); // We use a unique prefix to ensure it doesn't collide w/ strings not from getObjectId()
13
+ result = (lastObjectId++); // We use a unique prefix to ensure it doesn't collide w/ strings not from getObjectId()
32
14
  objectIds.set(obj, result)
33
15
  }
34
16
  return result;
@@ -41,12 +23,20 @@ export function getObjectId(obj, prefix=null) {
41
23
  * Adding a toJSON method globally on these object prototypes doesn't incur that performance penalty. */
42
24
  let isHashing = true;
43
25
  function toJSON() {
44
- //return (isHashing && !Array.isArray(this)) ? getObjectId(this) : this
45
26
  return isHashing ? getObjectId(this) : this
46
27
  }
28
+
29
+
47
30
  // Node.prototype.toJSON = toJSON;
48
31
  // Function.prototype.toJSON = toJSON;
49
-
32
+ // Sometimes these get unassigned by Chrome and Brave 119, as well as Firefox, seemingly randomly!
33
+ // The same tests sometimes pass, sometimes fail, even after browser and OS restarts.
34
+ // So we check the assignments on every run of getObjectHash()
35
+ if (Node.prototype.toJSON !== toJSON) {
36
+ Node.prototype.toJSON = toJSON;
37
+ if (Function.prototype.toJSON !== toJSON) // Will it only unmap one but not the other?
38
+ Function.prototype.toJSON = toJSON;
39
+ }
50
40
 
51
41
  /**
52
42
  * Get a string that uniquely maps to the values of the given object.
@@ -55,25 +45,18 @@ function toJSON() {
55
45
  *
56
46
  * Relies on the Node and Function prototypes being overridden above.
57
47
  *
48
+ * Note that passing an integer may collide with the number we get from hashing an object.
49
+ * But we don't handle that case because we need max performance and Solarite never passes integers to this function.
50
+ *
58
51
  * @param obj {*}
59
52
  * @returns {string} */
60
53
  export function getObjectHash(obj) {
61
-
62
- // Sometimes these get unassigned by Chrome and Brave 119, as well as Firefox, seemingly randomly!
63
- // The same tests sometimes pass, sometimes fail, even after browser and OS restarts.
64
- // So we check the assignments on every run of getObjectHash()
65
- if (Node.prototype.toJSON !== toJSON) {
66
- Node.prototype.toJSON = toJSON;
67
- if (Function.prototype.toJSON !== toJSON) // Will it only unmap one but not the other?
68
- Function.prototype.toJSON = toJSON;
69
- }
70
-
71
54
  let result;
72
55
  isHashing = true;
73
56
  try {
74
57
  result = JSON.stringify(obj);
75
58
  }
76
- catch(e){
59
+ catch(e) {
77
60
  result = getObjectHashCircular(obj);
78
61
  }
79
62
  isHashing = false;
@@ -81,7 +64,7 @@ export function getObjectHash(obj) {
81
64
  }
82
65
 
83
66
  /**
84
- * Having this separate might help the optimzer for getObjectHash() ?
67
+ * Slower hashing method that supports.
85
68
  * @param obj
86
69
  * @returns {string} */
87
70
  function getObjectHashCircular(obj) {
package/src/solarite/r.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import Template from "./Template.js";
2
- import NodeGroupManager from "./NodeGroupManager.js";
2
+ import Util from "./Util.js";
3
+ import Globals from "./Globals.js";
3
4
 
4
5
  /**
5
6
  * Convert strings to HTMLNodes.
@@ -14,130 +15,139 @@ import NodeGroupManager from "./NodeGroupManager.js";
14
15
  * 5. TODO: list more
15
16
  *
16
17
  * Currently supported:
17
- * 1. r`<b>Hello${'World'}!` // Create Template that can later be used to create nodes.
18
+ * 1. r(el, options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
19
+ * 2. r(el, template, ?options) // Render the Template created by #1 to element.
18
20
  *
19
- * 2. r(el, template, ?options) // Render the template created by #1 to element.
20
- * 3. r(el, options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
21
+ * 3. r`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
21
22
  *
22
23
  * 4. r('Hello'); // Create single text node.
23
24
  * 5. r('<b>Hello</b>'); // Create single HTMLElement
24
25
  * 6. r('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
25
- * 7. r()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which includes properly handling nested components and r`` sub-expressions.
26
+ * 7. r()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which
27
+ * // includes properly handling nested components and r`` sub-expressions.
26
28
  * 8. r(template) // Render Template created by #1.
27
- * 9. r(() => r`<b>Hello</b>`); // Create dynamic element that has a render() function.
28
29
  *
29
- * @param htmlStrings {?HTMLElement|string|string[]|function():Template}
30
- * @param exprs {*[]|string|Template}
30
+ * 9. r({render(){...}}) // Pass an object with a render method, and optionally other props/methods.
31
+ *
32
+ * @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
33
+ * @param exprs {*[]|string|Template|Object}
31
34
  * @return {Node|HTMLElement|Template} */
32
35
  export default function r(htmlStrings=undefined, ...exprs) {
33
36
 
34
- // 1. Path if used as a template tag.
35
- if (Array.isArray(htmlStrings)) {
36
- return new Template(htmlStrings, exprs);
37
- }
38
-
39
- else if (htmlStrings instanceof Node) {
40
- let parent = htmlStrings, template = exprs[0];
41
-
42
- // 2. Render template created by #4 to element.
43
- if (exprs[0] instanceof Template) {
44
- let ngm = NodeGroupManager.get(parent);
45
- let options = exprs[1];
46
- ngm.render(template, options);
47
-
48
- // Append on the first go.
49
- if (!parent.childNodes.length && this) {
50
- // TODO: Is htis ever executed?
51
- debugger;
52
- parent.append(this.rootNg.getParentNode());
53
- }
54
- }
55
-
56
- // 3
57
- else if (!exprs.length || exprs[0]) {
58
- if (parent.shadowRoot)
59
- parent.innerHTML = ''; // Remove shadowroot. TODO: This could mess up paths?
60
-
61
- let options = exprs[0];
62
- return (htmlStrings, ...exprs) => {
63
- rendered.add(parent)
64
- let template = r(htmlStrings, ...exprs);
65
- let ngm = NodeGroupManager.get(parent);
66
- return ngm.render(template, options);
67
- }
68
- }
69
-
70
- // null for expr[0], remove whole element.
71
- else {
72
- let ngm = NodeGroupManager.get(parent);
73
- ngm.render(null, exprs[1])
74
- }
75
- }
76
-
77
- else if (typeof htmlStrings === 'string' || htmlStrings instanceof String) {
78
- // If it starts with a string, trim both ends.
79
- // TODO: Also trim if it ends with whitespace?
80
- if (htmlStrings.match(/^\s^</))
81
- htmlStrings = htmlStrings.trim();
82
-
83
- // We create a new one each time because otherwise
84
- // the returned fragment will have its content replaced by a subsequent call.
85
- let templateEl = document.createElement('template');
86
- templateEl.innerHTML = htmlStrings;
87
-
88
- // 4+5. Return Node if there's one child.
89
- if (templateEl.content.childNodes.length === 1)
90
- return templateEl.content.firstChild;
91
-
92
- // 6. Otherwise return DocumentFragment.
93
- return templateEl.content;
94
- }
95
-
96
- // 7. Create a static element
97
- else if (htmlStrings === undefined) {
98
- return (htmlStrings, ...exprs) => {
99
- //rendered.add(parent)
100
- let template = r(htmlStrings, ...exprs);
101
- return template.toNode();
102
- }
103
- }
104
-
105
- // 8.
106
- else if (htmlStrings instanceof Template) {
107
- let ngm = new NodeGroupManager();
108
- return ngm.render(htmlStrings);
109
- }
110
-
111
- // 9. Create dynamic element with render() function.
112
- else if (typeof htmlStrings === 'function') {
113
- let getTemplate = htmlStrings;
114
- let template = getTemplate();
115
-
116
- if (typeof template === 'string')
117
- throw new Error(`Please add the "r" prefix before the string "${template}"`)
118
-
119
- let ngm = new NodeGroupManager();
120
- template.replaceMode = true;
121
- let el = ngm.render(template);
122
-
123
- el.render = (function() {
124
- template = getTemplate();
125
- ngm.render(template)
126
- }).bind(el);
127
-
128
- return el;
129
- }
130
- else
131
- throw new Error('Unsupported arguments.')
132
- }
133
-
134
-
135
-
136
-
137
-
138
-
139
- /**
140
- * Elements that have been rendered to by r() at least once.
141
- * @type {WeakSet<HTMLElement>} */
142
- let rendered = new WeakSet();
143
- export {rendered}
37
+ // TODO: Make this a more flat if/else and call other functions for the logic.
38
+ if (htmlStrings instanceof Node) {
39
+ let parent = htmlStrings, template = exprs[0];
40
+
41
+ // 1
42
+ if (!(exprs[0] instanceof Template)) {
43
+ if (parent.shadowRoot)
44
+ parent.innerHTML = ''; // Remove shadowroot. TODO: This could mess up paths?
45
+
46
+ let options = exprs[0];
47
+
48
+ // Return a tagged template function that applies the tagged themplate to parent.
49
+ let taggedTemplate = (htmlStrings, ...exprs) => {
50
+ Globals.rendered.add(parent)
51
+ let template = new Template(htmlStrings, exprs);
52
+ return template.render(parent, options);
53
+ }
54
+ return taggedTemplate;
55
+ }
56
+
57
+ // 2. Render template created by #4 to element.
58
+ else if (exprs[0] instanceof Template) {
59
+ let options = exprs[1];
60
+ template.render(parent, options);
61
+
62
+ // Append on the first go.
63
+ if (!parent.childNodes.length && this) {
64
+ // TODO: Is this ever executed?
65
+ debugger;
66
+ parent.append(this.rootNg.getParentNode());
67
+ }
68
+ }
69
+
70
+
71
+
72
+ // null for expr[0], remove whole element.
73
+ // This path never happens?
74
+ else {
75
+ throw new Error('unsupported');
76
+ //let ngm = NodeGroupManager.get(parent);
77
+ //ngm.render(null, exprs[1])
78
+ }
79
+ }
80
+
81
+ // 3. Path if used as a template tag.
82
+ else if (Array.isArray(htmlStrings)) {
83
+ return new Template(htmlStrings, exprs);
84
+ }
85
+
86
+ else if (typeof htmlStrings === 'string' || htmlStrings instanceof String) {
87
+ // If it starts with a string, trim both ends.
88
+ // TODO: Also trim if it ends with whitespace?
89
+ if (htmlStrings.match(/^\s^</))
90
+ htmlStrings = htmlStrings.trim();
91
+
92
+ // We create a new one each time because otherwise
93
+ // the returned fragment will have its content replaced by a subsequent call.
94
+ let templateEl = document.createElement('template');
95
+ templateEl.innerHTML = htmlStrings;
96
+
97
+ // 4+5. Return Node if there's one child.
98
+ let relevantNodes = Util.trimEmptyNodes(templateEl.content.childNodes);
99
+ if (relevantNodes.length === 1)
100
+ return relevantNodes[0];
101
+
102
+ // 6. Otherwise return DocumentFragment.
103
+ return templateEl.content;
104
+ }
105
+
106
+ // 7. Create a static element
107
+ else if (htmlStrings === undefined) {
108
+ return (htmlStrings, ...exprs) => {
109
+ //Globals.rendered.add(parent)
110
+ let template = r(htmlStrings, ...exprs);
111
+ return template.render();
112
+ }
113
+ }
114
+
115
+ // 8.
116
+ else if (htmlStrings instanceof Template) {
117
+ return htmlStrings.render();
118
+ }
119
+
120
+
121
+ // 9. Create dynamic element with render() function.
122
+ else if (typeof htmlStrings === 'object') {
123
+ let obj = htmlStrings;
124
+
125
+ // Special rebound render path, called by normal path.
126
+ if (Globals.objToEl.has(obj)) {
127
+ return function(...args) {
128
+ let template = r(...args);
129
+ let el = template.render();
130
+ Globals.objToEl.set(obj, el);
131
+ }.bind(obj);
132
+ }
133
+
134
+ // Normal path
135
+ else {
136
+ Globals.objToEl.set(obj, null);
137
+ obj.render(); // Calls the Special rebound render path above, when the render function calls r(this)
138
+ let el = Globals.objToEl.get(obj);
139
+ Globals.objToEl.delete(obj);
140
+
141
+ for (let name in obj)
142
+ if (typeof obj[name] === 'function')
143
+ el[name] = obj[name].bind(el);
144
+ else
145
+ el[name] = obj[name];
146
+
147
+ return el;
148
+ }
149
+ }
150
+
151
+ else
152
+ throw new Error('Unsupported arguments.')
153
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Trying to be able to automatically watch primitive values.
3
+ * TODO:
4
+ * 1. Have get() return Proxies for nested updates.
5
+ * 2. Override .map() for loops to capture changes.
6
+ */
7
+
8
+
9
+ // Example:
10
+ /*
11
+ class WatchExample extends Solarite {
12
+
13
+ constructor(items = []) {
14
+ super();
15
+
16
+ this.items = items;
17
+ watch(this, 'items');
18
+
19
+ this.name = 'George';
20
+ watch(this, 'name');
21
+
22
+ this.render();
23
+ }
24
+
25
+ render() {
26
+ r(this)`
27
+ <watch-example>
28
+ ${() => this.name + '!'}
29
+
30
+ ${this.items.map(item => r`
31
+ <div>${item.name}</div>
32
+ `)}
33
+ ${this.items.length}
34
+ </watch-example>`;
35
+ }
36
+ }
37
+ customElements.define('watch-example', WatchExample);
38
+
39
+ let a = new WatchExample();
40
+
41
+ // Items is a Proxy.
42
+ // Calling push() will trigger the map'd ExprPaths to add another at the end.
43
+ // And the .length expression to update.
44
+ // Because accessing .items returns a proxy.
45
+ a.items.push({name: 'Fred'});
46
+ */
47
+
48
+
49
+ import Globals from "./Globals.js";
50
+ import delve from "../util/delve.js";
51
+ //import NodeGroupManager from "./NodeGroupManager.js";
52
+
53
+ let unusedArg = Symbol('unusedArg');
54
+
55
+ /**
56
+ *
57
+ * @param root {Object}
58
+ * @param path {string}
59
+ * @param value {string|Symbol} */
60
+ export default function watch3(root, path, value=unusedArg) {
61
+
62
+ /** @type {ExprPath} The ExprPath that's using this variable.*/
63
+ let exprPath;
64
+
65
+ /** @type {function} Function evaluated by the ExprPath. */
66
+ let exprFunction;
67
+
68
+ if (value !== unusedArg)
69
+ root[path] = value;
70
+
71
+ // Store internal value used by get/set.
72
+ else
73
+ value = root[path];
74
+
75
+ Object.defineProperty(root, path, {
76
+ get() {
77
+ // Track which ExprPath is using this variable.
78
+ if (Globals.currentExprPath)
79
+ [exprPath, exprFunction] = Globals.currentExprPath;
80
+
81
+ // TODO: Return Proxy for non-primitive value, to track path changed.
82
+ return value;
83
+ },
84
+ set(val) {
85
+ value = val;
86
+
87
+ if (exprFunction) {
88
+
89
+ // TODO: Will fail for attribute w/ a value having multiple ExprPaths.
90
+ // TODO: This won't update a component's expressions.
91
+ exprPath.apply(exprFunction);
92
+
93
+ exprPath.freeNodeGroups(); // TODO: This could be skipped if applyExprs() never marked them as in-use.
94
+
95
+ }
96
+ }
97
+ });
98
+ }