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
@@ -1,23 +1,24 @@
1
1
  import {assert} from "../util/Errors.js";
2
2
  import ExprPath, {PathType, getNodePath} from "./ExprPath.js";
3
-
4
3
  import {div, htmlContext, isEvent} from "./Util.js";
4
+ import Globals from "./Globals.js";
5
5
 
6
6
  /**
7
7
  * A Shell is created from a tagged template expression instantiated as Nodes,
8
- * but without any expressions filled in. */
8
+ * but without any expressions filled in.
9
+ * Only one Shell is created for all the items in a loop.
10
+ *
11
+ * When a NodeGroup is created from a Template's html strings,
12
+ * the NodeGroup then clones the Shell's fragmentn to be its nodes. */
9
13
  export default class Shell {
10
14
 
11
15
  /**
12
- * @type {DocumentFragment} Parent of the shell nodes. */
16
+ * @type {DocumentFragment} DOM parent of the shell nodes. */
13
17
  fragment;
14
18
 
15
19
  /** @type {ExprPath[]} Paths to where expressions should go. */
16
20
  paths = [];
17
21
 
18
- /** @type {?Template} Template that created this element. */
19
- template;
20
-
21
22
  // Embeds and ids
22
23
  events = [];
23
24
 
@@ -29,6 +30,7 @@ export default class Shell {
29
30
  staticComponents = [];
30
31
 
31
32
 
33
+
32
34
  /**
33
35
  * Create the nodes but without filling in the expressions.
34
36
  * This is useful because the expression-less nodes created by a template can be cached.
@@ -56,7 +58,7 @@ export default class Shell {
56
58
 
57
59
  // Swap out Embedded Solarite Components with ${} attributes.
58
60
  // Later, NodeGroup.render() will search for these and replace them with the real components.
59
- // Ctrl+F "redcomponent-placeholder" in project to find all code that manages subcomponents.
61
+ // Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
60
62
  if (context === htmlContext.Attribute) {
61
63
 
62
64
  let lastIndex, lastMatch;
@@ -66,7 +68,7 @@ export default class Shell {
66
68
  })
67
69
 
68
70
  if (lastMatch) {
69
- let newTagName = lastMatch + '-redcomponent-placeholder';
71
+ let newTagName = lastMatch + '-solarite-placeholder';
70
72
  lastHtml = lastHtml.slice(0, lastIndex) + newTagName + lastHtml.slice(lastIndex + lastMatch.length);
71
73
  componentNames[lastMatch] = newTagName
72
74
  }
@@ -85,7 +87,7 @@ export default class Shell {
85
87
  let template = document.createElement('template'); // Using a single global template won't keep the nodes as children of the DocumentFragment.
86
88
  let joinedHtml = buffer.join('');
87
89
 
88
- // Replace '-redcomponent-placeholder' close tags.
90
+ // Replace '-solarite-placeholder' close tags.
89
91
  // TODO: is there a better way? What if the close tag is inside a comment?
90
92
  for (let name in componentNames)
91
93
  joinedHtml = joinedHtml.replaceAll(`</${name}>`, `</${componentNames[name]}>`);
@@ -122,7 +124,9 @@ export default class Shell {
122
124
  let parts = attr.value.split(/[\ue000-\uf8ff]/g);
123
125
  if (parts.length > 1) {
124
126
  let nonEmptyParts = (parts.length === 2 && !parts[0].length && !parts[1].length) ? null : parts;
125
- this.paths.push(new ExprPath(null, node, PathType.Value, attr.name, nonEmptyParts));
127
+ let type = isEvent(attr.name) ? PathType.Event : PathType.Value;
128
+
129
+ this.paths.push(new ExprPath(null, node, type, attr.name, nonEmptyParts));
126
130
  node.setAttribute(attr.name, parts.join(''));
127
131
  }
128
132
  }
@@ -134,7 +138,7 @@ export default class Shell {
134
138
  // Get or create nodeBefore.
135
139
  let nodeBefore = node.previousSibling; // Can be the same as another Path's nodeMarker.
136
140
  if (!nodeBefore) {
137
- nodeBefore = document.createComment('PathStart:'+this.paths.length);
141
+ nodeBefore = document.createComment('ExprPath:'+this.paths.length);
138
142
  node.parentNode.insertBefore(nodeBefore, node)
139
143
  }
140
144
  /*#IFDEV*/assert(nodeBefore);/*#ENDIF*/
@@ -150,16 +154,14 @@ export default class Shell {
150
154
  // Re-use existing comment placeholder.
151
155
  else {
152
156
  nodeMarker = node;
153
- nodeMarker.textContent = 'PathEnd:'+ this.paths.length;
157
+ nodeMarker.textContent = 'ExprPathEnd:'+ this.paths.length;
154
158
  }
155
159
  /*#IFDEV*/assert(nodeMarker);/*#ENDIF*/
156
160
 
157
161
 
158
162
 
159
163
  let path = new ExprPath(nodeBefore, nodeMarker, PathType.Content);
160
- //#IFDEV
161
- path.parentIndex = this.paths.length; // For debugging.
162
- //#ENDIF
164
+
163
165
  this.paths.push(path);
164
166
  }
165
167
 
@@ -173,9 +175,6 @@ export default class Shell {
173
175
  for (let i=0; i<parts.length-1; i++) {
174
176
  let path = new ExprPath(node.previousSibling, node)
175
177
  path.type = PathType.Comment;
176
- //#IFDEV
177
- path.parentIndex = i; // For debugging.
178
- //#ENDIF
179
178
  this.paths.push(path);
180
179
  }
181
180
  }
@@ -194,10 +193,7 @@ export default class Shell {
194
193
  }
195
194
 
196
195
  for (let i=0, node; node=placeholders[i]; i++) {
197
- let path = new ExprPath(node.previousSibling, node, PathType.Content)
198
- //#IFDEV
199
- path.parentIndex = i; // For debugging.
200
- //#ENDIF
196
+ let path = new ExprPath(node.previousSibling, node, PathType.Content);
201
197
  this.paths.push(path);
202
198
 
203
199
  /*#IFDEV*/path.verify();/*#ENDIF*/
@@ -210,8 +206,8 @@ export default class Shell {
210
206
  }
211
207
  toRemove.map(el => el.remove());
212
208
 
213
- // Handle redcomponent-placeholder's.
214
- // Ctrl+F "redcomponent-placeholder" in project to find all code that manages subcomponents.
209
+ // Handle solarite-placeholder's.
210
+ // Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
215
211
  //if (componentNames.size)
216
212
  // this.components = [...this.fragment.querySelectorAll([...componentNames].join(','))]
217
213
 
@@ -228,22 +224,25 @@ export default class Shell {
228
224
  path.nodeMarkerPath = getNodePath(path.nodeMarker)
229
225
 
230
226
  // Cache so we don't have to calculate this later inside NodeGroup.applyExprs()
231
- if (path.type === PathType.Value && path.nodeMarker.nodeType === 1 &&
227
+ if (path.type === PathType.Value && path.nodeMarker.nodeType === 1 && /*path.nodeMarker !== template.content.children[0] &&*/
232
228
  (path.nodeMarker.tagName.includes('-') || path.nodeMarker.hasAttribute('is'))) {
233
229
  path.type = PathType.Component;
234
230
  }
235
231
  }
236
232
 
237
-
238
233
  this.findEmbeds();
239
234
 
240
-
241
235
  /*#IFDEV*/this.verify();/*#ENDIF*/
242
236
  } // end constructor
243
237
 
244
238
  /**
245
239
  * We find the path to every embed here once in the Shell, instead of every time a NodeGroup is instantiated.
246
- * When a Nodegroup is created, it calls NodeGroup.activateEmbeds() that uses these paths. */
240
+ * When a Nodegroup is created, it calls NodeGroup.activateEmbeds() that uses these paths.
241
+ * Populates:
242
+ * this.scripts
243
+ * this.styles
244
+ * this.ids
245
+ * this.staticComponents */
247
246
  findEmbeds() {
248
247
  this.scripts = Array.prototype.map.call(this.fragment.querySelectorAll('scripts'), el => getNodePath(el))
249
248
  this.styles = Array.prototype.map.call(this.fragment.querySelectorAll('style'), el => getNodePath(el))
@@ -255,7 +254,7 @@ export default class Shell {
255
254
  for (let el of idEls) {
256
255
  let id = el.getAttribute('data-id') || el.getAttribute('id')
257
256
  if (div.hasOwnProperty(id))
258
- throw new Error(`<${el.tagName.toLowerCase()} id="${id}"> can't override existing HTMLElement property.`)
257
+ throw new Error(`<${el.tagName.toLowerCase()} id="${id}"> can't override existing HTMLElement id property.`)
259
258
  }
260
259
 
261
260
 
@@ -280,13 +279,13 @@ export default class Shell {
280
279
 
281
280
  /**
282
281
  * Get the shell for the html strings.
283
- * @param htmlStrings {string[]}
282
+ * @param htmlStrings {string[]} Typically comes from a Template.
284
283
  * @returns {Shell} */
285
284
  static get(htmlStrings) {
286
- let result = shells.get(htmlStrings);
285
+ let result = Globals.shells.get(htmlStrings);
287
286
  if (!result) {
288
287
  result = new Shell(htmlStrings);
289
- shells.set(htmlStrings, result); // cache
288
+ Globals.shells.set(htmlStrings, result); // cache
290
289
  }
291
290
 
292
291
  /*#IFDEV*/result.verify();/*#ENDIF*/
@@ -304,4 +303,3 @@ export default class Shell {
304
303
  //#ENDIF
305
304
  }
306
305
 
307
- let shells = new WeakMap();
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Solarite JavasCript UI library.
3
+ * MIT License
4
+ * https://vorticode.github.io/solarite/
5
+ */
6
+
7
+
1
8
  import createSolarite from "./createSolarite.js";
2
9
 
3
10
  /**
@@ -14,6 +21,14 @@ let Solarite = new Proxy(createSolarite(), {
14
21
  export {Solarite}
15
22
  export {default as r} from './r.js';
16
23
  export {getArg, ArgType} from './getArg.js';
24
+ export {default as Template} from './Template.js';
25
+ export {default as Globals} from './Globals.js';
26
+
27
+ import Util from './Util.js';
28
+ let getInputValue = Util.getInputValue;
29
+ export {getInputValue};
30
+ export {default as delve} from '../util/delve.js';
17
31
 
18
- export {forEach, watchGet, watchSet} from './watch.js' // old, unfinished
19
- export {watch} from './watch2.js'; // unfinished
32
+ //Experimental:
33
+ //export {forEach, watchGet, watchSet} from './watch.js' // old, unfinished
34
+ //export {watch} from './watch2.js'; // unfinished
@@ -1,7 +1,7 @@
1
1
  import {assert} from "../util/Errors.js";
2
- import {getObjectId} from "./hash.js";
3
- import NodeGroupManager from "./NodeGroupManager.js";
4
-
2
+ import {getObjectHash, getObjectId} from "./hash.js";
3
+ import Globals from "./Globals.js";
4
+ import {RootNodeGroup} from "./NodeGroup.js";
5
5
 
6
6
  /**
7
7
  * The html strings and evaluated expressions from an html tagged template.
@@ -15,23 +15,18 @@ export default class Template {
15
15
  /** @type {string[]} */
16
16
  html = [];
17
17
 
18
- /**
19
- * If true, use this template to replace an existing element, instead of appending children to it.
20
- * @type {?boolean} */
21
- replaceMode;
22
-
23
- /** Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
18
+ /** @type {Array} Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
24
19
  hashedFields;
25
20
 
26
- /**
27
- * @deprecated
28
- * @type {ExprPath} Used with forEach() from watch.js
29
- * Set in NodeGroup.applyOneExpr() */
30
- parentPath;
31
-
21
+ /**
22
+ * @deprecated
23
+ * @type {ExprPath} Used with forEach() from watch.js
24
+ * Set in ExprPath.apply() */
25
+ parentPath;
26
+
32
27
  /** @type {NodeGroup} */
33
28
  nodeGroup;
34
-
29
+
35
30
  /**
36
31
  * @type {string[][]} */
37
32
  paths = [];
@@ -43,7 +38,7 @@ export default class Template {
43
38
  constructor(htmlStrings, exprs) {
44
39
  this.html = htmlStrings;
45
40
  this.exprs = exprs;
46
-
41
+
47
42
  //this.trace = new Error().stack.split(/\n/g)
48
43
 
49
44
  // Multiple templates can share the same htmlStrings array.
@@ -52,7 +47,7 @@ export default class Template {
52
47
  //#IFDEV
53
48
  assert(Array.isArray(htmlStrings))
54
49
  assert(Array.isArray(exprs))
55
-
50
+
56
51
  Object.defineProperty(this, 'debug', {
57
52
  get() {
58
53
  return JSON.stringify([this.html, this.exprs]);
@@ -66,20 +61,75 @@ export default class Template {
66
61
  * This prevents the hashed version from being too large. */
67
62
  toJSON() {
68
63
  if (!this.hashedFields)
69
- this.hashedFields = [getObjectId(this.html, 'Html'), this.exprs];
70
-
64
+ this.hashedFields = [getObjectId(this.html), this.exprs];
65
+
71
66
  return this.hashedFields
72
67
  }
73
68
 
74
- toNode() {
75
- let ngm = new NodeGroupManager();
76
- return ngm.render(this);
69
+ /**
70
+ * Render the main template, which may indirectly call renderTemplate() to create children.
71
+ * @param el {HTMLElement}
72
+ * @param options {RenderOptions}
73
+ * @return {?DocumentFragment|HTMLElement} */
74
+ render(el=null, options={}) {
75
+ let ng;
76
+ let standalone = !el;
77
+ let firstTime = false;
78
+
79
+ // Rendering a standalone element.
80
+ // TODO: figure out when to not use RootNodeGroup
81
+ if (standalone) {
82
+ ng = new RootNodeGroup(this, null, options);
83
+ el = ng.getRootNode();
84
+ Globals.nodeGroups.set(el, ng);
85
+ firstTime = true;
86
+ }
87
+ else {
88
+ ng = Globals.nodeGroups.get(el);
89
+ if (!ng) {
90
+ ng = new RootNodeGroup(this, el, options);
91
+ Globals.nodeGroups.set(el, ng);
92
+ firstTime = true;
93
+ }
94
+ }
95
+
96
+ // Creating the root nodegroup also renders it.
97
+ // If we didn't just create it, we need to render it.
98
+ if (!firstTime) {
99
+ if (this.html?.length === 1 && !this.html[0])
100
+ el.innerHTML = ''; // Fast path for empty component.
101
+ else
102
+ ng.applyExprs(this.exprs);
103
+ }
104
+
105
+ return el;
106
+ }
107
+
108
+ getExactKey() {
109
+ if (!this.exactKey)
110
+ this.exactKey = getObjectHash(this); // calls this.toJSON().
111
+ return this.exactKey;
77
112
  }
78
113
 
79
114
  getCloseKey() {
80
- // Use the joined html when debugging?
115
+ if (!this.closeKey)
116
+ this.closeKey = '@'+this.toJSON()[0];
117
+ // Use the joined html when debugging? But it breaks some tests.
81
118
  //return '@'+this.html.join('|')
82
119
 
83
- return '@'+this.hashedFields[0];
120
+ return this.closeKey;
84
121
  }
85
122
  }
123
+
124
+
125
+ /**
126
+ * @typedef {Object} RenderOptions
127
+ * @property {boolean=} styles - Replace :host in style tags to scope them locally.
128
+ * @property {boolean=} scripts - Execute script tags.
129
+ * @property {boolean=} ids - Create references to elements with id or data-id attributes.
130
+ * @property {?boolean} render - Deprecated.
131
+ * Used only when options are given to a class super constructor inheriting from Solarite.
132
+ * True to call render() immediately in super constructor.
133
+ * False to automatically call render() at all.
134
+ * Undefined (default) to call render() when added to the DOM, unless already rendered.
135
+ */
@@ -21,9 +21,133 @@ let Util = {
21
21
  child.textContent = newText;
22
22
  }
23
23
  }
24
- }
24
+ },
25
+
26
+ /**
27
+ * A generator function that recursively traverses and flattens a value.
28
+ *
29
+ * - If the input is an array, it recursively traverses and flattens the array.
30
+ * - If the input is a function, it calls the function, replaces the function
31
+ * with its result, and flattens the result if necessary. It will recursively
32
+ * call functions that return other functions.
33
+ * - Otherwise it yields the value as is.
34
+ *
35
+ * This function does not create a new array for the flattened values. Instead,
36
+ * it lazily yields each item as it is encountered. This can be more memory-efficient
37
+ * for large or deeply nested structures.
38
+ *
39
+ * @param {any} value - The value to flatten. Can be an array, object, function, or primitive.
40
+ * @yields {any} - The next item in the flattened structure.
41
+ *
42
+ * @example
43
+ * const complexArray = [
44
+ * 1,
45
+ * [2, () => 3, [4, () => [5, 6]], { a: 'object' }],
46
+ * () => () => 7,
47
+ * () => [() => 8, 9],
48
+ * ]; *
49
+ * for (const item of flatten(complexArray))
50
+ * console.log(item); // Outputs: 1, 2, 3, 4, 5, 6, { a: 'object' }, 7, 8, 9
51
+ */
52
+ *flatten(value) {
53
+ if (Array.isArray(value)) {
54
+ for (const item of value) {
55
+ yield* Util.flatten(item); // Recursively flatten arrays
56
+ }
57
+ } else if (typeof value === 'function') {
58
+ const result = value();
59
+ yield* Util.flatten(result); // Recursively flatten the result of a function
60
+ } else
61
+ yield value; // Yield primitive values as is
62
+ },
63
+
64
+ /**
65
+ * Get the value of an input as the most appropriate JavaScript type.
66
+ * @param node {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLDivElement}
67
+ * @return {string|string[]|number|[]|File[]|Date|boolean} */
68
+ getInputValue(node) {
69
+ if (node.type === 'checkbox' || node.type === 'radio')
70
+ return node.checked; // Boolean
71
+ if (node.type === 'file')
72
+ return [...node.files]; // FileList
73
+ if (node.type === 'number' || node.type === 'range')
74
+ return node.valueAsNumber; // Number
75
+ if (node.type === 'date' || node.type === 'time' || node.type === 'datetime-local')
76
+ return node.valueAsDate; // Date Object
77
+ if (node.type === 'select-multiple') // <select multiple>
78
+ return [...node.selectedOptions].map(option => option.value); // Array of Strings
79
+
80
+ return node.value; // String
81
+ },
82
+
83
+ /**
84
+ * Is it an array and a path that can be evaluated by delve() ?
85
+ * @param arr {Array|*}
86
+ * @returns {boolean} */
87
+ isPath(arr) {
88
+ return Array.isArray(arr) && typeof arr[0] === 'object' && !arr.slice(1).find(p => typeof p !== 'string' && typeof p !== 'number');
89
+ },
90
+
91
+ /**
92
+ * Find NodeGroups that had their nodes removed and add those nodes to a Fragment so
93
+ * they're not lost forever and the NodeGroup's internal structure is still consistent.
94
+ * This saves all of a NodeGroup's nodes in order, so that nextChildNode still works.
95
+ * This is necessary because a NodeGroup normally only stores the first and last node.
96
+ * Called from ExprPath.apply().
97
+ * @param oldNodeGroups {NodeGroup[]}
98
+ * @param oldNodes {Node[]} */
99
+ saveOrphans(oldNodeGroups, oldNodes) {
100
+ let oldNgMap = new Map();
101
+ for (let ng of oldNodeGroups) {
102
+ oldNgMap.set(ng.startNode, ng)
103
+
104
+ // TODO: Is this necessary?
105
+ // if (ng.parentPath)
106
+ // ng.parentPath.clearNodesCache();
107
+ }
25
108
 
109
+ for (let i=0, node; node = oldNodes[i]; i++) {
110
+ let ng;
111
+ if (!node.parentNode && (ng = oldNgMap.get(node))) {
112
+ //ng.nodesCache = [];
113
+ let fragment = document.createDocumentFragment();
114
+ let endNode = ng.endNode;
115
+ while (node !== endNode) {
116
+ fragment.append(node);
117
+ //ng.nodesCache.push(node);
118
+ i++;
119
+ node = oldNodes[i];
120
+ }
121
+ fragment.append(endNode);
122
+ //ng.nodesCache.push(endNode);
123
+ }
124
+ }
125
+ },
126
+
127
+ /**
128
+ * Remove nodes from the beginning and end that are not:
129
+ * 1. Elements.
130
+ * 2. Non-whitespace text nodes.
131
+ * @param nodes {Node[]|NodeList}
132
+ * @returns {Node[]} */
133
+ trimEmptyNodes(nodes) {
134
+ const shouldTrimNode = node =>
135
+ node.nodeType !== Node.ELEMENT_NODE &&
136
+ (node.nodeType !== Node.TEXT_NODE || node.textContent.trim() === '');
137
+
138
+ // Convert nodeList to an array for easier manipulation
139
+ const result = [...nodes]
140
+
141
+ // Trim from the start
142
+ while (result.length > 0 && shouldTrimNode(result[0]))
143
+ result.shift();
144
+
145
+ // Trim from the end
146
+ while (result.length > 0 && shouldTrimNode(result[result.length - 1]))
147
+ result.pop();
26
148
 
149
+ return result;
150
+ }
27
151
  };
28
152
 
29
153
  export default Util;
@@ -72,15 +196,15 @@ export function camelToDashes(str) {
72
196
  * Returns false if they're the same. Or the first index where they differ.
73
197
  * @param a
74
198
  * @param b
75
- * @returns {int|false} */
76
- export function findArrayDiff(a, b) {
77
- if (a.length !== b.length)
78
- return -1;
199
+ * @returns {boolean} */
200
+ export function arraySame(a, b) {
79
201
  let aLength = a.length;
202
+ if (aLength !== b.length)
203
+ return false;
80
204
  for (let i=0; i<aLength; i++)
81
205
  if (a[i] !== b[i])
82
- return i;
83
- return false; // the same.
206
+ return false;
207
+ return true; // the same.
84
208
  }
85
209
 
86
210
 
@@ -1,12 +1,16 @@
1
1
  import Util from "../util/Util.js";
2
- import {assert} from "../util/Errors.js";
2
+ //import {assert} from "../util/Errors.js";
3
3
  import delve from "../util/delve.js";
4
4
  import {getArg, ArgType} from "./getArg.js";
5
5
  import {getObjectHash} from "./hash.js";
6
- import NodeGroupManager from "./NodeGroupManager.js";
7
- import r, {rendered} from "./r.js";
6
+ //import NodeGroupManager from "./NodeGroupManager.js";
7
+ import r from "./r.js";
8
8
  import {camelToDashes} from "./Util.js";
9
- import {watchGet, watchSet} from "./watch.js";
9
+ import Globals from "./Globals.js";
10
+
11
+
12
+ //import {watchGet, watchSet} from "./watch.js";
13
+
10
14
 
11
15
 
12
16
  function defineClass(Class, tagName, extendsTag) {
@@ -23,14 +27,9 @@ function defineClass(Class, tagName, extendsTag) {
23
27
  }
24
28
  }
25
29
 
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
 
30
- /**
31
- * Store which instances of Solarite have already been added to the DOM. * @type {WeakSet<HTMLElement>}
32
- */
33
- let connected = new WeakSet();
31
+
32
+
34
33
 
35
34
  /**
36
35
  * Create a version of the Solarite class that extends from the given tag name.
@@ -39,8 +38,15 @@ let connected = new WeakSet();
39
38
  * 2. Calls render() when added to the DOM, if it hasn't been called already.
40
39
  * 3. Child elements are added before constructor is called. But they're also passed to the constructor.
41
40
  * 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.
41
+ * 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
42
+ * Can't figure out how to have these work standalone though, and still be synchronous.
43
43
  * 6. Can we extend from other element types like TR?
44
+ * 7. Shows default text if render() function isn't defined.
45
+ *
46
+ * Advantages to inheriting from HTMLElement
47
+ * 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
48
+ * 2. We can inherit from things like HTMLTableRowElement directly.
49
+ * 3. There's less magic, since everyone is familiar with defining custom elements.
44
50
  *
45
51
  * @param extendsTag {?string}
46
52
  * @return {Class} */
@@ -50,10 +56,10 @@ export default function createSolarite(extendsTag=null) {
50
56
  if (extendsTag && !extendsTag.includes('-')) {
51
57
  extendsTag = extendsTag.toLowerCase();
52
58
 
53
- BaseClass = elementClasses[extendsTag];
59
+ BaseClass = Globals.elementClasses[extendsTag];
54
60
  if (!BaseClass) { // TODO: Use Cache
55
61
  BaseClass = document.createElement(extendsTag).constructor;
56
- elementClasses[extendsTag] = BaseClass
62
+ Globals.elementClasses[extendsTag] = BaseClass
57
63
  }
58
64
  }
59
65
 
@@ -89,26 +95,24 @@ export default function createSolarite(extendsTag=null) {
89
95
  constructor(options={}) {
90
96
  super();
91
97
 
92
-
93
-
94
98
  // TODO: Is options.render ever used?
95
99
  if (options.render===true)
96
100
  this.render();
97
101
 
98
102
  else if (options.render===false)
99
- rendered.add(this); // Don't render on connectedCallback()
103
+ Globals.rendered.add(this); // Don't render on connectedCallback()
100
104
 
101
105
  // Add children before constructor code executes.
102
106
  // PendingChildren is setup in NodeGroup.createNewComponent()
103
107
  // TODO: Match named slots.
104
- let ch = NodeGroupManager.pendingChildren.pop();
108
+ let ch = Globals.pendingChildren.pop();
105
109
  if (ch)
106
110
  (this.querySelector('slot') || this).append(...ch);
107
111
 
108
-
112
+ /** @deprecated */
109
113
  Object.defineProperty(this, 'html', {
110
114
  set(html) {
111
- rendered.add(this);
115
+ Globals.rendered.add(this);
112
116
  if (typeof html === 'string') {
113
117
  console.warn("Assigning to this.html without the r template prefix.")
114
118
  this.innerHTML = html;
@@ -131,7 +135,7 @@ export default function createSolarite(extendsTag=null) {
131
135
  /**
132
136
  * Call render() only if it hasn't already been called. */
133
137
  renderFirstTime() {
134
- if (!rendered.has(this) && this.render)
138
+ if (!Globals.rendered.has(this) && this.render)
135
139
  this.render();
136
140
  }
137
141
 
@@ -139,8 +143,8 @@ export default function createSolarite(extendsTag=null) {
139
143
  * Called automatically by the browser. */
140
144
  connectedCallback() {
141
145
  this.renderFirstTime();
142
- if (!connected.has(this)) {
143
- connected.add(this);
146
+ if (!Globals.connected.has(this)) {
147
+ Globals.connected.add(this);
144
148
  this.onFirstConnect();
145
149
  }
146
150
  this.onConnect();
@@ -155,7 +159,9 @@ export default function createSolarite(extendsTag=null) {
155
159
  defineClass(this, tagName, extendsTag)
156
160
  }
157
161
 
162
+ //#IFDEV
158
163
 
164
+ /** @deprecated */
159
165
  renderWatched() {
160
166
  let ngm = NodeGroupManager.get(this);
161
167
 
@@ -229,8 +235,7 @@ export default function createSolarite(extendsTag=null) {
229
235
  // Create new NodeGroup
230
236
  let ng = ngm.getNodeGroup(template, false, true);
231
237
  ng.parentPath = beforeNg?.parentPath || loopInfo.template.parentPath;
232
- /*#IFDEV*/
233
- assert(ng.parentPath);/*#ENDIF*/
238
+
234
239
  for (let node of ng.getNodes())
235
240
  beforeNode.parentNode.insertBefore(node, beforeNode);
236
241
 
@@ -260,8 +265,10 @@ export default function createSolarite(extendsTag=null) {
260
265
  /**
261
266
  * @deprecated Use the getArg() function instead. */
262
267
  getArg(name, val=null, type=ArgType.String) {
268
+ throw new Error('deprecated');
263
269
  return getArg(this, name, val, type);
264
270
  }
271
+ //#ENDIF
265
272
  }
266
273
  }
267
274