solarite 0.1.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 (63) hide show
  1. package/build/build.bat +3 -0
  2. package/build/build.js +139 -0
  3. package/build/lib/rollup.min.js +11 -0
  4. package/build/lib/source-map.min.js +1 -0
  5. package/build/lib/terser.min.js +1 -0
  6. package/dist/Solarite-debug.js +4143 -0
  7. package/dist/Solarite.js +3740 -0
  8. package/dist/Solarite.min.js +4 -0
  9. package/docs/index.md +423 -0
  10. package/docs/js/Playground.js +184 -0
  11. package/docs/js/codemirror/codemirror6.js +32036 -0
  12. package/docs/js/codemirror/themeSolarIce.js +312 -0
  13. package/docs/js/documentation.js +32 -0
  14. package/docs/js/ui/CodeEditor.js +840 -0
  15. package/docs/js/ui/DarkToggle.js +52 -0
  16. package/docs/js/ui/FlexResizer.js +142 -0
  17. package/docs/js/util/Draggable2.js +151 -0
  18. package/docs/js/util/Errors.js +9 -0
  19. package/docs/js/util/Html.js +147 -0
  20. package/docs/js/util/Icons.js +623 -0
  21. package/docs/js/util/Input.js +253 -0
  22. package/docs/js/util/Util.js +88 -0
  23. package/docs/js/util/delve.js +43 -0
  24. package/docs/media/FiraCode400.woff2 +0 -0
  25. package/docs/media/cabin-latin-700.woff2 +0 -0
  26. package/docs/media/documentation.css +93 -0
  27. package/docs/media/eternium.css +1123 -0
  28. package/docs/media/solarite-machine.webp +0 -0
  29. package/index.html +325 -0
  30. package/package.json +33 -0
  31. package/readme.md +3 -0
  32. package/src/solarite/ExprPath.js +554 -0
  33. package/src/solarite/MultiValueMap.js +65 -0
  34. package/src/solarite/NodeGroup.js +706 -0
  35. package/src/solarite/NodeGroupManager.js +582 -0
  36. package/src/solarite/Shell.js +307 -0
  37. package/src/solarite/Solarite.js +19 -0
  38. package/src/solarite/Template.js +85 -0
  39. package/src/solarite/Util.js +264 -0
  40. package/src/solarite/createSolarite.js +267 -0
  41. package/src/solarite/getArg.js +99 -0
  42. package/src/solarite/hash.js +101 -0
  43. package/src/solarite/r.js +143 -0
  44. package/src/solarite/udomdiff.js +233 -0
  45. package/src/solarite/watch.js +302 -0
  46. package/src/solarite/watch2.js +439 -0
  47. package/src/unused/FastLookupArray.js +54 -0
  48. package/src/unused/Hashes.js +339 -0
  49. package/src/unused/InUse.test.js +92 -0
  50. package/src/unused/InUseMap.js +98 -0
  51. package/src/unused/LinkedList.js +117 -0
  52. package/src/unused/LinkedList.test.js +115 -0
  53. package/src/unused/Perf.js +47 -0
  54. package/src/unused/Template.js +108 -0
  55. package/src/util/Errors.js +9 -0
  56. package/src/util/Util.js +88 -0
  57. package/src/util/delve.js +43 -0
  58. package/tests/Benchmark.test.js +319 -0
  59. package/tests/NodeGroup.test.js +115 -0
  60. package/tests/Shell.test.js +75 -0
  61. package/tests/Solarite.test.js +2896 -0
  62. package/tests/Testimony.js +602 -0
  63. package/tests/index.html +75 -0
@@ -0,0 +1,267 @@
1
+ import Util from "../util/Util.js";
2
+ import {assert} from "../util/Errors.js";
3
+ import delve from "../util/delve.js";
4
+ import {getArg, ArgType} from "./getArg.js";
5
+ import {getObjectHash} from "./hash.js";
6
+ import NodeGroupManager from "./NodeGroupManager.js";
7
+ import r, {rendered} from "./r.js";
8
+ import {camelToDashes} from "./Util.js";
9
+ import {watchGet, watchSet} from "./watch.js";
10
+
11
+
12
+ function defineClass(Class, tagName, extendsTag) {
13
+ if (!customElements.getName(Class)) { // If not previously defined.
14
+ tagName = tagName || camelToDashes(Class.name)
15
+ if (!tagName.includes('-'))
16
+ tagName += '-element';
17
+
18
+ let options = null;
19
+ if (extendsTag)
20
+ options = {extends: extendsTag}
21
+
22
+ customElements.define(tagName, Class, options)
23
+ }
24
+ }
25
+
26
+ /**
27
+ * @type {Object<string, Class<Node>>} A map from built-in tag names to the constructors that create them. */
28
+ let elementClasses = {};
29
+
30
+ /**
31
+ * Store which instances of Solarite have already been added to the DOM. * @type {WeakSet<HTMLElement>}
32
+ */
33
+ let connected = new WeakSet();
34
+
35
+ /**
36
+ * Create a version of the Solarite class that extends from the given tag name.
37
+ * Reasons to inherit from this instead of HTMLElement. None of these are all that useful.
38
+ * 1. customElements.define() is called automatically when you create the first instance.
39
+ * 2. Calls render() when added to the DOM, if it hasn't been called already.
40
+ * 3. Child elements are added before constructor is called. But they're also passed to the constructor.
41
+ * 4. We can use this.html = r`...` to set html.
42
+ * 5. We have the onConnect, onFirstConnect, and onDisconnect methods. These could be standalone though.
43
+ * 6. Can we extend from other element types like TR?
44
+ *
45
+ * @param extendsTag {?string}
46
+ * @return {Class} */
47
+ export default function createSolarite(extendsTag=null) {
48
+
49
+ let BaseClass = HTMLElement;
50
+ if (extendsTag && !extendsTag.includes('-')) {
51
+ extendsTag = extendsTag.toLowerCase();
52
+
53
+ BaseClass = elementClasses[extendsTag];
54
+ if (!BaseClass) { // TODO: Use Cache
55
+ BaseClass = document.createElement(extendsTag).constructor;
56
+ elementClasses[extendsTag] = BaseClass
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Intercept the construct call to auto-define the class before the constructor is called.
62
+ * @type {HTMLElement} */
63
+ let HTMLElementAutoDefine = new Proxy(BaseClass, {
64
+ construct(Parent, args, Class) {
65
+ defineClass(Class, null, extendsTag)
66
+
67
+ // This is a good place to manipulate any args before they're sent to the constructor.
68
+ // Such as loading them from attributes, if I could find a way to do so.
69
+
70
+ // This line is equivalent the to super() call.
71
+ return Reflect.construct(Parent, args, Class);
72
+ }
73
+ });
74
+
75
+ return class Solarite extends HTMLElementAutoDefine {
76
+
77
+
78
+ /**
79
+ * TODO: Make these standalone functions.
80
+ * Callbacks.
81
+ * Use onConnect.push(() => ...); to add new callbacks. */
82
+ onConnect = Util.callback();
83
+
84
+ onFirstConnect = Util.callback();
85
+ onDisconnect = Util.callback();
86
+
87
+ /**
88
+ * @param options {RenderOptions} */
89
+ constructor(options={}) {
90
+ super();
91
+
92
+
93
+
94
+ // TODO: Is options.render ever used?
95
+ if (options.render===true)
96
+ this.render();
97
+
98
+ else if (options.render===false)
99
+ rendered.add(this); // Don't render on connectedCallback()
100
+
101
+ // Add children before constructor code executes.
102
+ // PendingChildren is setup in NodeGroup.createNewComponent()
103
+ // TODO: Match named slots.
104
+ let ch = NodeGroupManager.pendingChildren.pop();
105
+ if (ch)
106
+ (this.querySelector('slot') || this).append(...ch);
107
+
108
+
109
+ Object.defineProperty(this, 'html', {
110
+ set(html) {
111
+ rendered.add(this);
112
+ if (typeof html === 'string') {
113
+ console.warn("Assigning to this.html without the r template prefix.")
114
+ this.innerHTML = html;
115
+ }
116
+ else
117
+ this.modifications = r(this, html, options);
118
+ }
119
+ })
120
+
121
+ /*
122
+ let pthis = new Proxy(this, {
123
+ get(obj, prop) {
124
+ return Reflect.get(obj, prop)
125
+ }
126
+ });
127
+ this.render = this.render.bind(pthis);
128
+ */
129
+ }
130
+
131
+ /**
132
+ * Call render() only if it hasn't already been called. */
133
+ renderFirstTime() {
134
+ if (!rendered.has(this) && this.render)
135
+ this.render();
136
+ }
137
+
138
+ /**
139
+ * Called automatically by the browser. */
140
+ connectedCallback() {
141
+ this.renderFirstTime();
142
+ if (!connected.has(this)) {
143
+ connected.add(this);
144
+ this.onFirstConnect();
145
+ }
146
+ this.onConnect();
147
+ }
148
+
149
+ disconnectedCallback() {
150
+ this.onDisconnect();
151
+ }
152
+
153
+
154
+ static define(tagName=null) {
155
+ defineClass(this, tagName, extendsTag)
156
+ }
157
+
158
+
159
+ renderWatched() {
160
+ let ngm = NodeGroupManager.get(this);
161
+
162
+ let nodeGroupUpdates = [];
163
+
164
+ for (let change of ngm.changes) {
165
+ if (change.action === 'set') {
166
+ for (let transformerInfo of change.transformerInfo) {
167
+
168
+ let oldHash = transformerInfo.hash;
169
+
170
+ let newObj = delve(watchSet(transformerInfo.path[0]), transformerInfo.path.slice(1));
171
+ let newTemplate = transformerInfo.transformer(newObj);
172
+ let newHash = getObjectHash(newTemplate);
173
+ let ngs = [...ngm.nodeGroupsAvailable.data[oldHash]];
174
+ for (let ng of ngs) {
175
+ nodeGroupUpdates.push([ng, oldHash, newHash, newTemplate.exprs, transformerInfo]);
176
+ }
177
+ }
178
+ }
179
+
180
+ else if (change.action === 'delete') {
181
+ for (let hash of change.value) {
182
+ let ngs = [...ngm.nodeGroupsAvailable.getAll(hash)]; // deletes from nodeGroupsAvailable.
183
+
184
+ for (let ng of ngs) {
185
+ if (ng.parentPath)
186
+ ng.parentPath.clearNodesCache();
187
+
188
+ for (let node of ng.getNodes())
189
+ node.remove();
190
+
191
+ // TODO: Update ancestor NodeGroup exactKeys
192
+ }
193
+ }
194
+ }
195
+ else if (change.action === 'insert') {
196
+
197
+ let beforeNg = change.beforeTemplate ? ngm.getNodeGroup(change.beforeTemplate, true) : null;
198
+ let arrayPath = [change.root, ...change.path];
199
+
200
+ // Get anchor so we can use it to get the parent
201
+ // TODO: Should this be watchGet(change.root) ?
202
+ for (let loopInfo of ngm.getLoopInfo([change.root, ...change.path.slice(0, -1)])) {
203
+
204
+ // Change.extra is aTemplate telling us where to insert before.
205
+ let beforeNode = beforeNg?.startNode || loopInfo.template.parentPath.nodeMarker;
206
+
207
+ // Loop over every item added to the array.
208
+ let i = 0; // TODO: How to get real insert index.
209
+ for (let obj of change.value) {
210
+
211
+ // Same logic as forEach() function.
212
+
213
+ let callback = loopInfo.itemTransformer;
214
+ let path = [...arrayPath.slice(0, -1), (arrayPath.at(-1) * 1 + i) + ''];
215
+
216
+ // Shortened logic found in watchGet(), but not any faster?
217
+ // the watchSet() is what makes this slower!
218
+ // let obj = delve(watchSet(path[0]), path.slice(1));
219
+ // let template = callback(obj);
220
+ // let serializedPath = serializePath(path);
221
+ // pathToTransformer.add(serializedPath, new TransformerInfo(path, callback, template)); // Uses a Set() to ensure no duplicates.
222
+
223
+ let template = watchGet(path, callback);
224
+ i++;
225
+
226
+
227
+ //let template = loopInfo.itemTransformer(obj); // What if it takes more than one obj argument?
228
+
229
+ // Create new NodeGroup
230
+ let ng = ngm.getNodeGroup(template, false, true);
231
+ ng.parentPath = beforeNg?.parentPath || loopInfo.template.parentPath;
232
+ /*#IFDEV*/
233
+ assert(ng.parentPath);/*#ENDIF*/
234
+ for (let node of ng.getNodes())
235
+ beforeNode.parentNode.insertBefore(node, beforeNode);
236
+
237
+ if (ng.parentPath) // This check is needed for the forEachSpliceInsert test, but why?
238
+ ng.parentPath.clearNodesCache();
239
+ }
240
+
241
+ // TODO: Update ancestor NodeGroup exactKeys
242
+ }
243
+ }
244
+ }
245
+
246
+ // Update them all at once, that way we can reassign the same value twice.
247
+ for (let [ng, oldHash, newHash, exprs, ti] of nodeGroupUpdates) {
248
+ ng.applyExprs(exprs);
249
+ ngm.nodeGroupsAvailable.data[oldHash].delete(ng);
250
+ ng.exactKey = ti.hash = newHash;
251
+ ngm.nodeGroupsAvailable.add(ng.exactKey, ng); // Add back to Map with new key.
252
+ }
253
+
254
+
255
+ ngm.changes = [];
256
+
257
+ return []; // TODO
258
+ }
259
+
260
+ /**
261
+ * @deprecated Use the getArg() function instead. */
262
+ getArg(name, val=null, type=ArgType.String) {
263
+ return getArg(this, name, val, type);
264
+ }
265
+ }
266
+ }
267
+
@@ -0,0 +1,99 @@
1
+
2
+
3
+
4
+ /**
5
+ * There are three ways to create an instance of a Solarite Component:
6
+ * 1. new ComponentName(); // direct class instantiation
7
+ * 2. this.html = r`<div><component-name></component-name></div>; // as a child of another RedComponent.
8
+ * 3. <body><component-name></component-name></body> // in the Document html.
9
+ *
10
+ * When created via #3, Solarite has no way to pass attributes as arguments to the constructor. So to make
11
+ * sure we get the correct value via all three paths, we write our constructors according to the following
12
+ * example. Note that constructor args are embedded in an object, and must be all lower-case because
13
+ * Browsers make all html attribute names lowercase.
14
+ *
15
+ * @example
16
+ * constructor({name, userid=1}={}) {
17
+ * super();
18
+ *
19
+ * // Get value from "name" attriute if persent, otherwise from name constructor arg.
20
+ * this.name = getArg(this, 'name', name);
21
+ *
22
+ * // Optionally convert the value to an integer.
23
+ * this.userId = getArg(this, 'userid', userid, ArgType.Int);
24
+ * }
25
+ *
26
+ * @param el {HTMLElement}
27
+ * @param name {string} Attribute name. Not case-sensitive.
28
+ * @param val {*} Default value to use if attribute doesn't exist.
29
+ * @param type {ArgType|function|*[]}
30
+ * If an array, use the value if it's in the array, otherwise return undefined.
31
+ * If it's a function, pass the value to the function and return the result.
32
+ * @return {*} */
33
+ export function getArg(el, name, val=null, type=ArgType.String) {
34
+ let attrVal = el.getAttribute(name);
35
+ if (attrVal !== null) // If attribute doesn't exist.
36
+ val = attrVal;
37
+
38
+ if (Array.isArray(type))
39
+ return type.includes(val) ? val : undefined;
40
+
41
+ if (typeof type === 'function')
42
+ return type(val);
43
+
44
+ // If bool, it's true as long as it exists and its value isn't falsey.
45
+ if (type===ArgType.Bool) {
46
+ let lAttrVal = typeof val === 'string' ? val.toLowerCase() : val;
47
+ return !['false', '0', false, 0, null, undefined].includes(lAttrVal);
48
+ }
49
+
50
+ // Attribute doesn't exist
51
+ switch (type) {
52
+ case ArgType.Int:
53
+ return parseInt(val);
54
+ case ArgType.Float:
55
+ return parseFloat(val);
56
+ case ArgType.String:
57
+ return val || '';
58
+ case ArgType.JSON:
59
+ try {
60
+ return JSON.parse(val);
61
+ } catch (e) {
62
+ return val;
63
+ }
64
+ case ArgType.Eval:
65
+ try {
66
+ return eval(`(${val})`);
67
+ } catch (e) {
68
+ return val;
69
+ }
70
+ default:
71
+ return val;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * @enum */
77
+ var ArgType = {
78
+
79
+ /**
80
+ * false, 0, null, undefined, '0', and 'false' (case-insensitive) become false.
81
+ * Anything else, including empty string becomes true.
82
+ * Empty string is true because attributes with no value should be evaulated as true. */
83
+ Bool: 'Bool',
84
+
85
+ Int: 'Int',
86
+ Float: 'Float',
87
+ String: 'String',
88
+
89
+ /**
90
+ * Parse the string value as JSON.
91
+ * If it's not parsable, return the value as a string. */
92
+ JSON: 'JSON',
93
+
94
+ /**
95
+ * Evaluate the string as JavaScript using the eval() function.
96
+ * If it can't be evaluated, return the original string. */
97
+ Eval: 'Eval'
98
+ }
99
+ export {ArgType};
@@ -0,0 +1,101 @@
1
+ let lastObjectId = 1>>>0; // Is a 32-bit int faster to increment than JavaScript's Number, which is a 64-bit float?
2
+ let objectIds = new WeakMap();
3
+
4
+ /**
5
+ * @param obj {Object|string|Node}
6
+ * @param prefix
7
+ * @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
+
26
+ // if (typeof obj === 'function')
27
+ // return obj.toString();
28
+
29
+ let result = objectIds.get(obj);
30
+ 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()
32
+ objectIds.set(obj, result)
33
+ }
34
+ return result;
35
+ }
36
+
37
+ /**
38
+ * Control how JSON.stringify() handles Nodes and Functions.
39
+ * Normally, we'd pass a replacer() function argument to JSON.stringify() to handle Nodes and Functions.
40
+ * But that makes JSON.stringify() take twice as long to run.
41
+ * Adding a toJSON method globally on these object prototypes doesn't incur that performance penalty. */
42
+ let isHashing = true;
43
+ function toJSON() {
44
+ //return (isHashing && !Array.isArray(this)) ? getObjectId(this) : this
45
+ return isHashing ? getObjectId(this) : this
46
+ }
47
+ // Node.prototype.toJSON = toJSON;
48
+ // Function.prototype.toJSON = toJSON;
49
+
50
+
51
+ /**
52
+ * Get a string that uniquely maps to the values of the given object.
53
+ * If a value in obj changes, calling getObjectHash(obj) will then return a different hash.
54
+ * This is used by NodeGroupManager to create a hash that represents the current values of a NodeGroup.
55
+ *
56
+ * Relies on the Node and Function prototypes being overridden above.
57
+ *
58
+ * @param obj {*}
59
+ * @returns {string} */
60
+ 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
+ let result;
72
+ isHashing = true;
73
+ try {
74
+ result = JSON.stringify(obj);
75
+ }
76
+ catch(e){
77
+ result = getObjectHashCircular(obj);
78
+ }
79
+ isHashing = false;
80
+ return result;
81
+ }
82
+
83
+ /**
84
+ * Having this separate might help the optimzer for getObjectHash() ?
85
+ * @param obj
86
+ * @returns {string} */
87
+ function getObjectHashCircular(obj) {
88
+
89
+ //console.log('circular')
90
+ // Slower version that handles circular references.
91
+ // Just adding any callback at all, even one that just returns the value, makes JSON.stringify() twice as slow.
92
+ const seen = new Set();
93
+ return JSON.stringify(obj, (key, value) => {
94
+ if (typeof value === 'object' && value !== null) {
95
+ if (seen.has(value))
96
+ return getObjectId(value);
97
+ seen.add(value);
98
+ }
99
+ return value;
100
+ });
101
+ }
@@ -0,0 +1,143 @@
1
+ import Template from "./Template.js";
2
+ import NodeGroupManager from "./NodeGroupManager.js";
3
+
4
+ /**
5
+ * Convert strings to HTMLNodes.
6
+ * Using r as a tag will always create a Template.
7
+ * Using r() as a function() will always create a DOM element.
8
+ *
9
+ * Features beyond what standard js tagged template strings do:
10
+ * 1. r`` sub-expressions
11
+ * 2. functions, nodes, and arrays of nodes as sub-expressions.
12
+ * 3. html-escape all expressions by default, unless wrapped in r()
13
+ * 4. event binding
14
+ * 5. TODO: list more
15
+ *
16
+ * Currently supported:
17
+ * 1. r`<b>Hello${'World'}!` // Create Template that can later be used to create nodes.
18
+ *
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
+ *
22
+ * 4. r('Hello'); // Create single text node.
23
+ * 5. r('<b>Hello</b>'); // Create single HTMLElement
24
+ * 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
+ * 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
+ * @param htmlStrings {?HTMLElement|string|string[]|function():Template}
30
+ * @param exprs {*[]|string|Template}
31
+ * @return {Node|HTMLElement|Template} */
32
+ export default function r(htmlStrings=undefined, ...exprs) {
33
+
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}