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.
- package/benchmarks/naive/Solarite.min.js +4 -0
- package/benchmarks/naive/index.html +14 -0
- package/benchmarks/naive/main.js +339 -0
- package/benchmarks/naive/package-lock.json +13 -0
- package/benchmarks/naive/package.json +23 -0
- package/benchmarks/readme.md +48 -0
- package/build/build.bat +3 -2
- package/build/build.js +1 -1
- package/dist/Solarite-debug.js +1941 -2791
- package/dist/Solarite.js +1697 -2511
- package/dist/Solarite.min.js +3 -3
- package/dist/udomdiff-license.txt +18 -0
- package/docs/index.md +695 -235
- package/docs/js/Playground.js +1 -1
- package/docs/js/codemirror/codemirror6.js +3683 -3206
- package/docs/js/codemirror/themeSolarIce.js +2 -2
- package/docs/js/documentation.js +2 -2
- package/docs/js/ui/CodeEditor.js +183 -45
- package/docs/js/ui/FlexResizer.js +15 -5
- package/docs/js/util/Errors.js +11 -0
- package/docs/media/documentation.css +6 -3
- package/index.html +462 -26
- package/package.json +1 -1
- package/readme.md +11 -1
- package/src/solarite/ExprPath.js +422 -135
- package/src/solarite/Globals.js +53 -0
- package/src/solarite/NodeGroup.js +300 -388
- package/src/solarite/Shell.js +31 -33
- package/src/solarite/Solarite.js +17 -2
- package/src/solarite/Template.js +75 -25
- package/src/solarite/Util.js +131 -7
- package/src/solarite/createSolarite.js +32 -25
- package/src/solarite/getArg.js +12 -12
- package/src/solarite/hash.js +18 -35
- package/src/solarite/r.js +128 -118
- package/src/solarite/watch3.js +98 -0
- package/src/{solarite → unused}/NodeGroupManager.js +86 -224
- package/src/unused/onConnect.js +79 -0
- package/src/{solarite → unused}/watch.js +2 -2
- package/src/{solarite → unused}/watch2.js +4 -4
- package/src/{solarite → util}/MultiValueMap.js +23 -12
- package/src/util/WeakArray.js +33 -0
- package/tests/Solarite.test.js +1108 -166
- package/tests/Testimony.js +274 -67
- package/tests/index.html +6 -35
- package/tests/run.bat +2 -0
- package/tests/NodeGroup.test.js +0 -115
- package/tests/Shell.test.js +0 -75
package/src/solarite/Shell.js
CHANGED
|
@@ -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}
|
|
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 "
|
|
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 + '-
|
|
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 '-
|
|
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
|
-
|
|
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('
|
|
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 = '
|
|
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
|
-
|
|
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
|
|
214
|
-
// Ctrl+F "
|
|
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();
|
package/src/solarite/Solarite.js
CHANGED
|
@@ -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
|
-
|
|
19
|
-
export {
|
|
32
|
+
//Experimental:
|
|
33
|
+
//export {forEach, watchGet, watchSet} from './watch.js' // old, unfinished
|
|
34
|
+
//export {watch} from './watch2.js'; // unfinished
|
package/src/solarite/Template.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {assert} from "../util/Errors.js";
|
|
2
|
-
import {getObjectId} from "./hash.js";
|
|
3
|
-
import
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
70
|
-
|
|
64
|
+
this.hashedFields = [getObjectId(this.html), this.exprs];
|
|
65
|
+
|
|
71
66
|
return this.hashedFields
|
|
72
67
|
}
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
*/
|
package/src/solarite/Util.js
CHANGED
|
@@ -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 {
|
|
76
|
-
export function
|
|
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
|
|
83
|
-
return
|
|
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
|
|
6
|
+
//import NodeGroupManager from "./NodeGroupManager.js";
|
|
7
|
+
import r from "./r.js";
|
|
8
8
|
import {camelToDashes} from "./Util.js";
|
|
9
|
-
import
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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
|
|