solarite 0.3.1 → 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/src/h.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import Template from "./Template.js";
2
- import Util from "./Util.js";
3
2
  import Globals from "./Globals.js";
4
- import {assert} from "./assert.js";
3
+ import toEl from "./toEl.js";
5
4
 
6
5
  /**
7
6
  * Convert strings to HTMLNodes.
@@ -11,173 +10,126 @@ import {assert} from "./assert.js";
11
10
  * Features beyond what standard js tagged template strings do:
12
11
  * 1. r`` sub-expressions
13
12
  * 2. functions, nodes, and arrays of nodes as sub-expressions.
14
- * 3. html-escape all expressions by default, unless wrapped in r()
13
+ * 3. html-escape all expressions by default, unless wrapped in h()
15
14
  * 4. event binding
16
15
  * 5. TODO: list more
17
16
  *
17
+ * General rule:
18
+ * If h() is a function with null or an HTMLElement as its first argument create a Node.
19
+ * Otherwise create a template
20
+ *
18
21
  * Currently supported:
19
- * 1. h(el, options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
20
- * 2. h(el, template, ?options) // Render the Template created by #1 to element.
21
22
  *
22
- * 3. h`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
23
+ * Create Tempataes
24
+ * 1. h`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
25
+ * 2. h('<b>Hello</b><u>Goodbye</u>'); // Create Template from string, that can later be used to create nodes.
26
+ *
27
+ * Add children to an element.
28
+ * 3. h(el, h`<b>${'Hi'}</b>`, ?options)
29
+ * 4. h(el, ?options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
23
30
  *
24
- * 4. h('Hello'); // Create single text node.
25
- * 5. h('<b>Hello</b>'); // Create single HTMLElement
26
- * 6. h('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
27
- * 7. h()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which
28
- * // includes properly handling nested components and r`` sub-expressions.
29
- * 8. h(template) // Render Template created by #1.
31
+ * Create top-level element
32
+ * 5. h()`Hello<b>${'World'}!</b>`
30
33
  *
31
- * 9. h({render(){...}}) // Pass an object with a render method, and optionally other props/methods.
32
- * 10. h(string, object, ...) // JSX TODO
34
+ * 6. h(string, object, ...) // Used for JSX
33
35
  * @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
34
36
  * @param exprs {*[]|string|Template|Object}
35
37
  * @return {Node|HTMLElement|Template} */
36
38
  export default function h(htmlStrings=undefined, ...exprs) {
37
39
 
38
- if (htmlStrings === undefined && !exprs.length && arguments.length)
39
- throw new Error('h() cannot be called with undefined.');
40
-
41
- // TODO: Make this a more flat if/else and call other functions for the logic.
42
- if (htmlStrings instanceof Node) {
43
- let parent = htmlStrings, template = exprs[0];
44
-
45
- // 1
46
- if (!(exprs[0] instanceof Template)) {
47
- if (parent.shadowRoot)
48
- parent.innerHTML = ''; // Remove shadowroot. TODO: This could mess up paths?
49
-
50
- let options = exprs[0];
51
-
52
- // Return a tagged template function that applies the tagged themplate to parent.
53
- let taggedTemplate = (htmlStrings, ...exprs) => {
54
- Globals.rendered.add(parent)
55
- let template = new Template(htmlStrings, exprs);
56
- return template.render(parent, options);
57
- }
58
- return taggedTemplate;
59
- }
60
-
61
- // 2. Render template created by #4 to element.
62
- else { // instanceof Template
63
- let options = exprs[1];
64
- template.render(parent, options);
65
-
66
- // Append on the first go.
67
- if (!parent.childNodes.length && this) {
68
- // TODO: Is this ever executed?
69
- debugger;
70
- parent.append(this.rootNg.getParentNode());
71
- }
72
- }
40
+ // 1. Tagged template
41
+ if (Array.isArray(arguments[0])) {
42
+ return new Template(arguments[0], exprs);
73
43
  }
74
44
 
75
- // 3. Path if used as a template tag.
76
- else if (Array.isArray(htmlStrings)) {
77
- return new Template(htmlStrings, exprs);
78
- }
45
+ // 2. String to template, or JSX factory form h(tag, props, ...children)
46
+ else if (typeof arguments[0] === 'string' || arguments[0] instanceof String) {
47
+ let tagOrHtml = arguments[0];
79
48
 
80
- else if (typeof htmlStrings === 'string' || htmlStrings instanceof String) {
81
- // 10. JSX
82
- if (typeof exprs[0] === 'object') {
83
- let tag = htmlStrings;
49
+ // 2a. JSX: h("tag", {props}, ...children)
50
+ if (exprs.length && (typeof exprs[0] === 'object' || exprs[0] === null)) {
51
+ let tag = tagOrHtml + '';
84
52
  let props = exprs[0] || {};
85
53
  let children = exprs.slice(1);
86
54
 
87
- let templateHtmlStrings = [];
88
- let templateExprs = [];
89
-
90
- // TODO How to know which children are static html and which are expression placeholders?
91
- // Perhaps we have to treat every text child as a string?
55
+ return Template.fromJsx(tag, props, children);
56
+ }
92
57
 
93
- assert(templateHtmlStrings.length === templateExprs.length+1);
94
- return new Template(templateHtmlStrings, templateExprs);
58
+ // 2b. Plain html string => template
59
+ else {
60
+ let html = tagOrHtml;
61
+ // If it starts with whitespace, trim both ends.
62
+ // TODO: Also trim if it ends with whitespace?
63
+ if (html.match(/^\s^</))
64
+ html = html.trim();
65
+ return new Template([html], []);
95
66
  }
67
+ }
96
68
 
69
+ else if (arguments[0] instanceof HTMLElement || arguments[0] instanceof DocumentFragment) {
97
70
 
98
- // If it starts with a string, trim both ends.
99
- // TODO: Also trim if it ends with whitespace?
100
- if (htmlStrings.match(/^\s^</))
101
- htmlStrings = htmlStrings.trim();
71
+ // 3. Render template to element.
72
+ if (arguments[1] instanceof Template) {
102
73
 
103
- // We create a new one each time because otherwise
104
- // the returned fragment will have its content replaced by a subsequent call.
105
- let templateEl = document.createElement('template');
106
- templateEl.innerHTML = htmlStrings;
74
+ /** @type Template */
75
+ let template = arguments[1];
76
+ let parent = arguments[0];
77
+ let options = arguments[2]; // deprecated?
78
+ template.render(parent, options);
79
+ }
107
80
 
108
- // 4+5. Return Node if there's one child.
109
- let relevantNodes = Util.trimEmptyNodes(templateEl.content.childNodes);
110
- if (relevantNodes.length === 1)
111
- return relevantNodes[0];
81
+ // 4. Render tagged template to element
82
+ else {
83
+ let parent = arguments[0], options = arguments[1];
112
84
 
113
- // 6. Otherwise return DocumentFragment.
114
- return templateEl.content;
115
- }
85
+ // Remove shadowroot. TODO: This could mess up paths?
86
+ if (parent.shadowRoot)
87
+ parent.innerHTML = '';
116
88
 
117
- // 7. Create a static element
118
- else if (htmlStrings === undefined) {
119
- return (htmlStrings, ...exprs) => {
120
- //Globals.rendered.add(parent)
121
- let template = h(htmlStrings, ...exprs);
122
- return template.render();
89
+ // Return a tagged template function that applies the tagged template to parent.
90
+ let taggedTemplate = (htmlStrings, ...exprs) => {
91
+ Globals.rendered.add(parent)
92
+ let template = new Template(htmlStrings, exprs);
93
+ return template.render(parent, options);
94
+ }
95
+ return taggedTemplate;
123
96
  }
124
97
  }
125
98
 
126
- // 8.
127
- else if (htmlStrings instanceof Template) {
128
- return htmlStrings.render();
99
+ // 5. Create a static element h()'<div></div>' (Deprecated?)
100
+ else if (!arguments.length) {
101
+ return (htmlStrings, ...exprs) => {
102
+ let template = h(htmlStrings, ...exprs);
103
+ return toEl(template); // Go to path 6.
104
+ }
129
105
  }
130
106
 
131
-
132
- // 9. Create dynamic element with render() function.
107
+ // 6. Help toEl() with objects.
108
+ // Special rebound render path, called by normal path.
109
+ // Intercepts the main h(this)`...` function call inside render().
133
110
  // TODO: This path doesn't handle embeds like data-id="..."
134
- else if (typeof htmlStrings === 'object') {
135
- let obj = htmlStrings;
111
+ else if (typeof arguments[0] === 'object' && Globals.objToEl.has(arguments[0])) {
112
+ let obj = arguments[0];
136
113
 
137
- if (obj.constructor.name !== 'Object')
114
+ if (obj.constructor.name !== 'Object')
138
115
  throw new Error(`Solarate Web Component class ${obj.constructor?.name} must extend HTMLElement.`);
139
116
 
117
+ // Jsx with h(this, <jsx>)
118
+ if (arguments[1] instanceof Template) {
119
+ let template = arguments[1];
120
+ let el = template.render();
121
+ Globals.objToEl.set(obj, el);
122
+ }
140
123
 
141
- // Special rebound render path, called by normal path.
142
- // Intercepts the main r`...` function call inside render().
143
- if (Globals.objToEl.has(obj)) {
124
+ // h(this)`<div>...</div>
125
+ else
144
126
  return function(...args) {
145
- let template = h(...args);
146
- let el = template.render();
127
+ let template = h(...args);
128
+ let el = template.render();
147
129
  Globals.objToEl.set(obj, el);
148
130
  }.bind(obj);
149
- }
150
-
151
- // Normal path
152
- else {
153
- Globals.objToEl.set(obj, null);
154
- obj[renderF](); // Calls the Special rebound render path above, when the render function calls r(this)
155
- let el = Globals.objToEl.get(obj);
156
- Globals.objToEl.delete(obj);
157
-
158
- for (let name in obj)
159
- if (typeof obj[name] === 'function')
160
- el[name] = obj[name].bind(el); // Make the "this" of functions be el.
161
- // TODO: But this doesn't work for passing an object with functions as a constructor arg via an attribute:
162
- // <my-element arg=${{myFunc() { return this }}}
163
- else
164
- el[name] = obj[name];
165
-
166
- // Bind id's
167
- // This doesn't work for id's referenced by attributes.
168
- // for (let idEl of el.querySelectorAll('[id],[data-id]')) {
169
- // Util.bindId(el, idEl);
170
- // Util.bindId(obj, idEl);
171
- // }
172
- // TODO: Bind styles
173
-
174
- return el;
175
- }
176
131
  }
177
-
178
132
  else
179
- throw new Error('Unsupported arguments.')
133
+ throw new Error('h() does not support argument of type: ' + (arguments[0] ? typeof arguments[0] : arguments[0]))
180
134
  }
181
135
 
182
- // Trick to prevent minifier from renaming this function.
183
- let renderF = 'render';
package/src/toEl.js ADDED
@@ -0,0 +1,84 @@
1
+ import Globals from "./Globals.js";
2
+ import {Template} from "./Solarite.js";
3
+ import Util from "./Util.js";
4
+
5
+ /**
6
+ * Convert a template, string, or object into a DOM Node or Element
7
+ *
8
+ * 1. h('Hello'); // Create single text node.
9
+ * 2. h('<b>Hello</b>'); // Create single HTMLElement
10
+ * 3. h('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
11
+ * 4. h(template) // Render Template created by h`<html>` or h();
12
+ * 5. h({render(){...}}) // Pass an object with a render method, and optionally other props/methods.
13
+ * @param arg {string|Template|{render:()=>void}}
14
+ * @returns {Node|DocumentFragment|HTMLElement} */
15
+ export default function toEl(arg) {
16
+
17
+ if (typeof arg === 'string') {
18
+ let html = arg;
19
+
20
+ // If it's an element with whitespace before or after it, trim both ends.
21
+ if (html.match(/^\s^</) || html.match(/>\s+$/))
22
+ html = html.trim();
23
+
24
+ // We create a new one each time because otherwise
25
+ // the returned fragment will have its content replaced by a subsequent call.
26
+ let templateEl = Globals.doc.createElement('template');
27
+ templateEl.innerHTML = html;
28
+
29
+ // 1+2. Return Node if there's one child.
30
+ let relevantNodes = Util.trimEmptyNodes(templateEl.content.childNodes);
31
+ if (relevantNodes.length === 1)
32
+ return relevantNodes[0];
33
+
34
+ // 3. Otherwise return DocumentFragment.
35
+ return templateEl.content;
36
+ }
37
+
38
+ // 4.
39
+ if (arg instanceof Template) {
40
+ return arg.render();
41
+ }
42
+
43
+ // 5. Create dynamic element from an object with a render() function.
44
+ // TODO: This path doesn't handle embeds like data-id="..."
45
+ else if (arg && typeof arg === 'object') {
46
+ let obj = arg;
47
+
48
+ if (obj.constructor.name !== 'Object')
49
+ throw new Error(`Solarate Web Component class ${obj.constructor?.name} must extend HTMLElement.`);
50
+
51
+ // Normal path
52
+ if (!Globals.objToEl.has(obj)) {
53
+ Globals.objToEl.set(obj, null);
54
+ obj[renderF](); // Calls the Special rebound render path above, when the render function calls h(this)
55
+ let el = Globals.objToEl.get(obj);
56
+ Globals.objToEl.delete(obj);
57
+
58
+ for (let name in obj)
59
+ if (typeof obj[name] === 'function')
60
+ el[name] = obj[name].bind(el); // Make the "this" of functions be el.
61
+ // TODO: But this doesn't work for passing an object with functions as a constructor arg via an attribute:
62
+ // <my-element arg=${{myFunc() { return this }}}
63
+ else
64
+ el[name] = obj[name];
65
+
66
+ // Bind id's
67
+ // This doesn't work for id's referenced by attributes.
68
+ // for (let idEl of el.querySelectorAll('[id],[data-id]')) {
69
+ // Util.bindId(el, idEl);
70
+ // Util.bindId(obj, idEl);
71
+ // }
72
+ // TODO: Bind styles
73
+
74
+ return el;
75
+ }
76
+ }
77
+
78
+ throw new Error('toEl() does not support argument of type: ' + (arg ? typeof arg : arg));
79
+
80
+ }
81
+
82
+
83
+ // Trick to prevent minifier from renaming this function.
84
+ let renderF = 'render';
package/src/udomdiff.js CHANGED
@@ -28,11 +28,6 @@ import NodeGroup from "./NodeGroup.js";
28
28
  * @returns {Node[]} The same list of future children.
29
29
  */
30
30
  const udomdiff = (parentNode, a, b, before) => {
31
- //#IFDEV
32
- // if (parentNode instanceof ExprPath)
33
- // parentNode.verify();
34
- //#ENDIF
35
-
36
31
  const bLength = b.length;
37
32
  let aEnd = a.length;
38
33
  let bEnd = bLength;
@@ -54,13 +49,6 @@ const udomdiff = (parentNode, a, b, before) => {
54
49
  while (bStart < bEnd) {
55
50
  let bNode = b[bStart++];
56
51
  parentNode.insertBefore(bNode, node);
57
-
58
- //#IFDEV
59
- if (bNode instanceof NodeGroup)
60
- bNode.verify();
61
- // if (parentNode instanceof ExprPath)
62
- // parentNode.verify();
63
- //#ENDIF
64
52
  }
65
53
  }
66
54
  // remove head or tail: fast path
@@ -70,13 +58,6 @@ const udomdiff = (parentNode, a, b, before) => {
70
58
  let aNode = a[aStart];
71
59
  if (!map || !map.has(aNode)) {
72
60
  parentNode.removeChild(aNode);
73
-
74
- //#IFDEV
75
- if (aNode instanceof NodeGroup)
76
- aNode.verify();
77
- // if (parentNode instanceof ExprPath)
78
- // parentNode.verify();
79
- //#ENDIF
80
61
  }
81
62
  aStart++;
82
63
  }
@@ -113,24 +94,10 @@ const udomdiff = (parentNode, a, b, before) => {
113
94
  a2,
114
95
  b2.nextSibling
115
96
  );
116
- //#IFDEV
117
- if (a2 instanceof NodeGroup)
118
- a2.verify();
119
- // if (parentNode instanceof ExprPath)
120
- // parentNode.verify();
121
- //#ENDIF
122
97
 
123
98
  let bNode = b[--bEnd];
124
99
  parentNode.insertBefore(bNode, node);
125
100
 
126
- //#IFDEV
127
- if (bNode instanceof NodeGroup)
128
- bNode.verify();
129
- // if (parentNode instanceof ExprPath)
130
- // parentNode.verify();
131
-
132
- //#ENDIF
133
-
134
101
  // mark the future index as identical (yeah, it's dirty, but cheap 👍)
135
102
  // The main reason to do this, is that when a[aEnd] will be reached,
136
103
  // the loop will likely be on the fast path, as identical to b[bEnd].
@@ -178,14 +145,6 @@ const udomdiff = (parentNode, a, b, before) => {
178
145
  while (bStart < index) {
179
146
  let bNode = b[bStart++];
180
147
  parentNode.insertBefore(bNode, node);
181
-
182
- //#IFDEV
183
- if (bNode instanceof NodeGroup)
184
- bNode.verify();
185
- // if (parentNode instanceof ExprPath)
186
- // parentNode.verify();
187
-
188
- //#ENDIF
189
148
  }
190
149
  }
191
150
  // if the effort wasn't good enough, fallback to a replace,
@@ -198,13 +157,6 @@ const udomdiff = (parentNode, a, b, before) => {
198
157
  bNode,
199
158
  aNode
200
159
  );
201
-
202
- //#IFDEV
203
- if (aNode instanceof NodeGroup)
204
- aNode.verify();
205
- // if (parentNode instanceof ExprPath)
206
- // parentNode.verify();
207
- //#ENDIF
208
160
  }
209
161
  }
210
162
  // otherwise move the source forward, 'cause there's nothing to do
@@ -217,13 +169,6 @@ const udomdiff = (parentNode, a, b, before) => {
217
169
  else {
218
170
  let aNode = a[aStart++];
219
171
  parentNode.removeChild(aNode);
220
-
221
- //#IFDEV
222
- if (aNode instanceof NodeGroup)
223
- aNode.verify();
224
- // if (parentNode instanceof ExprPath)
225
- // parentNode.verify();
226
- //#ENDIF
227
172
  }
228
173
  }
229
174
  }
package/src/watch.js CHANGED
@@ -28,7 +28,7 @@ class WatchExample extends Solarite {
28
28
  }
29
29
 
30
30
  render() {
31
- r(this)`
31
+ h(this)`
32
32
  <watch-example>
33
33
  ${() => this.name + '!'}
34
34
 
@@ -397,7 +397,7 @@ class ProxyHandler {
397
397
  * TODO
398
398
  *
399
399
  *
400
- * @param root {HTMLElement} An instance of a Web Component that uses r() to render its content.
400
+ * @param root {HTMLElement} An instance of a Web Component that uses h() to render its content.
401
401
  * @param field {string} The name of a top-level property of root.
402
402
  * @param value {string|Symbol} The default value. */
403
403
  export default function watch(root, field, value=unusedArg) {