solarite 0.1.1 → 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 +2 -2
- package/build/build.js +1 -1
- package/dist/Solarite-debug.js +1344 -1532
- package/dist/Solarite.js +1298 -1334
- package/dist/Solarite.min.js +3 -3
- package/dist/udomdiff-license.txt +18 -0
- package/docs/index.md +440 -130
- package/docs/js/Playground.js +1 -1
- package/docs/js/codemirror/codemirror6.js +3683 -3206
- package/docs/js/codemirror/themeSolarIce.js +1 -1
- package/docs/js/documentation.js +1 -1
- package/docs/js/ui/CodeEditor.js +183 -45
- package/docs/js/ui/FlexResizer.js +14 -3
- package/docs/js/util/Errors.js +11 -0
- package/docs/media/documentation.css +6 -3
- package/index.html +449 -30
- package/package.json +1 -1
- package/readme.md +1 -1
- package/src/solarite/ExprPath.js +349 -151
- package/src/solarite/Globals.js +43 -1
- package/src/solarite/NodeGroup.js +275 -293
- package/src/solarite/Shell.js +26 -28
- package/src/solarite/Solarite.js +12 -0
- package/src/solarite/Template.js +55 -128
- package/src/solarite/Util.js +131 -7
- package/src/solarite/createSolarite.js +14 -19
- package/src/solarite/getArg.js +1 -1
- package/src/solarite/hash.js +18 -35
- package/src/solarite/r.js +127 -127
- package/src/solarite/watch3.js +18 -11
- package/src/{solarite → unused}/NodeGroupManager.js +81 -109
- package/src/{solarite → unused}/watch.js +1 -1
- package/src/{solarite → unused}/watch2.js +1 -2
- package/src/{solarite → util}/MultiValueMap.js +18 -11
- package/src/util/WeakArray.js +33 -0
- package/tests/Solarite.test.js +862 -167
- package/tests/index.html +4 -2
- package/tests/run.bat +1 -1
- package/deno.lock +0 -176
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.
|
|
@@ -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*/
|
|
@@ -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
|
/**
|
|
@@ -15,7 +22,12 @@ export {Solarite}
|
|
|
15
22
|
export {default as r} from './r.js';
|
|
16
23
|
export {getArg, ArgType} from './getArg.js';
|
|
17
24
|
export {default as Template} from './Template.js';
|
|
25
|
+
export {default as Globals} from './Globals.js';
|
|
18
26
|
|
|
27
|
+
import Util from './Util.js';
|
|
28
|
+
let getInputValue = Util.getInputValue;
|
|
29
|
+
export {getInputValue};
|
|
30
|
+
export {default as delve} from '../util/delve.js';
|
|
19
31
|
|
|
20
32
|
//Experimental:
|
|
21
33
|
//export {forEach, watchGet, watchSet} from './watch.js' // old, unfinished
|
package/src/solarite/Template.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {assert} from "../util/Errors.js";
|
|
2
|
-
import {getObjectId} from "./hash.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
2
|
+
import {getObjectHash, getObjectId} from "./hash.js";
|
|
3
|
+
import Globals from "./Globals.js";
|
|
4
|
+
import {RootNodeGroup} from "./NodeGroup.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* The html strings and evaluated expressions from an html tagged template.
|
|
@@ -16,12 +15,7 @@ export default class Template {
|
|
|
16
15
|
/** @type {string[]} */
|
|
17
16
|
html = [];
|
|
18
17
|
|
|
19
|
-
/**
|
|
20
|
-
* If true, use this template to replace an existing element, instead of appending children to it.
|
|
21
|
-
* @type {?boolean} */
|
|
22
|
-
replaceMode;
|
|
23
|
-
|
|
24
|
-
/** 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. */
|
|
25
19
|
hashedFields;
|
|
26
20
|
|
|
27
21
|
/**
|
|
@@ -29,10 +23,10 @@ export default class Template {
|
|
|
29
23
|
* @type {ExprPath} Used with forEach() from watch.js
|
|
30
24
|
* Set in ExprPath.apply() */
|
|
31
25
|
parentPath;
|
|
32
|
-
|
|
26
|
+
|
|
33
27
|
/** @type {NodeGroup} */
|
|
34
28
|
nodeGroup;
|
|
35
|
-
|
|
29
|
+
|
|
36
30
|
/**
|
|
37
31
|
* @type {string[][]} */
|
|
38
32
|
paths = [];
|
|
@@ -44,7 +38,7 @@ export default class Template {
|
|
|
44
38
|
constructor(htmlStrings, exprs) {
|
|
45
39
|
this.html = htmlStrings;
|
|
46
40
|
this.exprs = exprs;
|
|
47
|
-
|
|
41
|
+
|
|
48
42
|
//this.trace = new Error().stack.split(/\n/g)
|
|
49
43
|
|
|
50
44
|
// Multiple templates can share the same htmlStrings array.
|
|
@@ -53,7 +47,7 @@ export default class Template {
|
|
|
53
47
|
//#IFDEV
|
|
54
48
|
assert(Array.isArray(htmlStrings))
|
|
55
49
|
assert(Array.isArray(exprs))
|
|
56
|
-
|
|
50
|
+
|
|
57
51
|
Object.defineProperty(this, 'debug', {
|
|
58
52
|
get() {
|
|
59
53
|
return JSON.stringify([this.html, this.exprs]);
|
|
@@ -67,8 +61,8 @@ export default class Template {
|
|
|
67
61
|
* This prevents the hashed version from being too large. */
|
|
68
62
|
toJSON() {
|
|
69
63
|
if (!this.hashedFields)
|
|
70
|
-
this.hashedFields = [getObjectId(this.html
|
|
71
|
-
|
|
64
|
+
this.hashedFields = [getObjectId(this.html), this.exprs];
|
|
65
|
+
|
|
72
66
|
return this.hashedFields
|
|
73
67
|
}
|
|
74
68
|
|
|
@@ -78,131 +72,64 @@ export default class Template {
|
|
|
78
72
|
* @param options {RenderOptions}
|
|
79
73
|
* @return {?DocumentFragment|HTMLElement} */
|
|
80
74
|
render(el=null, options={}) {
|
|
81
|
-
|
|
82
75
|
let ng;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
ngm.modifications = {
|
|
94
|
-
created: [],
|
|
95
|
-
updated: [],
|
|
96
|
-
moved: [],
|
|
97
|
-
deleted: []
|
|
98
|
-
};
|
|
99
|
-
//#ENDIF
|
|
100
|
-
|
|
101
|
-
ngm.options = options;
|
|
102
|
-
ngm.clearSubscribers = false; // Used for deprecated watch() path?
|
|
103
|
-
ngm.mutationWatcherEnabled = false;
|
|
104
|
-
|
|
105
|
-
// Fast path for empty component.
|
|
106
|
-
if (this.html?.length === 1 && !this.html[0]) {
|
|
107
|
-
el.innerHTML = '';
|
|
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;
|
|
108
86
|
}
|
|
109
87
|
else {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!exact) {
|
|
116
|
-
close = ngm.getNodeGroup(this, false);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
let firstTime = !ngm.rootNg;
|
|
120
|
-
ngm.rootNg = exact || close;
|
|
121
|
-
|
|
122
|
-
// Reparent NodeGroup
|
|
123
|
-
// TODO: Move this to NodeGroup?
|
|
124
|
-
let parent = ngm.rootNg.getParentNode();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// If this is the first time rendering this element.
|
|
128
|
-
if (firstTime) {
|
|
129
|
-
|
|
130
|
-
// Save slot children
|
|
131
|
-
let fragment;
|
|
132
|
-
if (el.childNodes.length) {
|
|
133
|
-
fragment = document.createDocumentFragment();
|
|
134
|
-
fragment.append(...el.childNodes);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Add rendered elements.
|
|
138
|
-
if (parent instanceof DocumentFragment)
|
|
139
|
-
el.append(parent);
|
|
140
|
-
else if (parent)
|
|
141
|
-
el.append(...parent.childNodes)
|
|
142
|
-
|
|
143
|
-
// Apply slot children
|
|
144
|
-
if (fragment) {
|
|
145
|
-
for (let slot of el.querySelectorAll('slot[name]')) {
|
|
146
|
-
let name = slot.getAttribute('name')
|
|
147
|
-
if (name)
|
|
148
|
-
slot.append(...fragment.querySelectorAll(`[slot='${name}']`))
|
|
149
|
-
}
|
|
150
|
-
let unamedSlot = el.querySelector('slot:not([name])')
|
|
151
|
-
if (unamedSlot)
|
|
152
|
-
unamedSlot.append(fragment)
|
|
153
|
-
}
|
|
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;
|
|
154
93
|
}
|
|
94
|
+
}
|
|
155
95
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// Remove old attributes
|
|
165
|
-
// for (let attrib of this.rootEl.attributes)
|
|
166
|
-
// if (attrib.name !== 'is' && attrib.name !== 'data-style' && !ng.pseudoRoot.hasAttribute(attrib.name))
|
|
167
|
-
// this.rootEl.removeAttribute(attrib.name)
|
|
168
|
-
|
|
169
|
-
// Add/set new attributes
|
|
170
|
-
if (firstTime)
|
|
171
|
-
for (let attrib of ng.pseudoRoot.attributes)
|
|
172
|
-
if (!el.hasAttribute(attrib.name))
|
|
173
|
-
el.setAttribute(attrib.name, attrib.value);
|
|
174
|
-
|
|
175
|
-
// ng.startNode = ng.endNode = this.rootEl;
|
|
176
|
-
// ng.nodesCache = [ng.startNode]
|
|
177
|
-
// for (let path of ng.paths) {
|
|
178
|
-
// if (path.nodeMarker === ng.rootEl)
|
|
179
|
-
// path.nodeMarker = this.rootEl;
|
|
180
|
-
// path.nodesCache = null;
|
|
181
|
-
// /*#IFDEV*/assert(path.nodeBefore !== ng.rootEl)/*#ENDIF*/
|
|
182
|
-
// }
|
|
183
|
-
//
|
|
184
|
-
// ng.rootEl = this.rootEl;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/*#IFDEV*/ngm.rootNg.verify();/*#ENDIF*/
|
|
188
|
-
ngm.reset(); // Mark all NodeGroups as available, for next render.
|
|
189
|
-
/*#IFDEV*/ngm.rootNg.verify();/*#ENDIF*/
|
|
190
|
-
|
|
191
|
-
window.ngm = ngm;
|
|
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);
|
|
192
103
|
}
|
|
193
104
|
|
|
194
|
-
ngm.mutationWatcherEnabled = true;
|
|
195
105
|
return el;
|
|
196
|
-
//#IFDEV
|
|
197
|
-
//return ngm.modifications;
|
|
198
|
-
//#ENDIF
|
|
199
106
|
}
|
|
200
107
|
|
|
108
|
+
getExactKey() {
|
|
109
|
+
if (!this.exactKey)
|
|
110
|
+
this.exactKey = getObjectHash(this); // calls this.toJSON().
|
|
111
|
+
return this.exactKey;
|
|
112
|
+
}
|
|
201
113
|
|
|
202
114
|
getCloseKey() {
|
|
203
|
-
|
|
115
|
+
if (!this.closeKey)
|
|
116
|
+
this.closeKey = '@'+this.toJSON()[0];
|
|
117
|
+
// Use the joined html when debugging? But it breaks some tests.
|
|
204
118
|
//return '@'+this.html.join('|')
|
|
205
119
|
|
|
206
|
-
return
|
|
120
|
+
return this.closeKey;
|
|
207
121
|
}
|
|
208
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
|
|
|
@@ -3,9 +3,10 @@ import Util from "../util/Util.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 Globals from "./Globals.js";
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
//import {watchGet, watchSet} from "./watch.js";
|
|
@@ -26,14 +27,9 @@ function defineClass(Class, tagName, extendsTag) {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
/**
|
|
30
|
-
* @type {Object<string, Class<Node>>} A map from built-in tag names to the constructors that create them. */
|
|
31
|
-
let elementClasses = {};
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*/
|
|
36
|
-
let connected = new WeakSet();
|
|
31
|
+
|
|
32
|
+
|
|
37
33
|
|
|
38
34
|
/**
|
|
39
35
|
* Create a version of the Solarite class that extends from the given tag name.
|
|
@@ -45,6 +41,7 @@ let connected = new WeakSet();
|
|
|
45
41
|
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
46
42
|
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
47
43
|
* 6. Can we extend from other element types like TR?
|
|
44
|
+
* 7. Shows default text if render() function isn't defined.
|
|
48
45
|
*
|
|
49
46
|
* Advantages to inheriting from HTMLElement
|
|
50
47
|
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
@@ -59,10 +56,10 @@ export default function createSolarite(extendsTag=null) {
|
|
|
59
56
|
if (extendsTag && !extendsTag.includes('-')) {
|
|
60
57
|
extendsTag = extendsTag.toLowerCase();
|
|
61
58
|
|
|
62
|
-
BaseClass = elementClasses[extendsTag];
|
|
59
|
+
BaseClass = Globals.elementClasses[extendsTag];
|
|
63
60
|
if (!BaseClass) { // TODO: Use Cache
|
|
64
61
|
BaseClass = document.createElement(extendsTag).constructor;
|
|
65
|
-
elementClasses[extendsTag] = BaseClass
|
|
62
|
+
Globals.elementClasses[extendsTag] = BaseClass
|
|
66
63
|
}
|
|
67
64
|
}
|
|
68
65
|
|
|
@@ -98,26 +95,24 @@ export default function createSolarite(extendsTag=null) {
|
|
|
98
95
|
constructor(options={}) {
|
|
99
96
|
super();
|
|
100
97
|
|
|
101
|
-
|
|
102
|
-
|
|
103
98
|
// TODO: Is options.render ever used?
|
|
104
99
|
if (options.render===true)
|
|
105
100
|
this.render();
|
|
106
101
|
|
|
107
102
|
else if (options.render===false)
|
|
108
|
-
rendered.add(this); // Don't render on connectedCallback()
|
|
103
|
+
Globals.rendered.add(this); // Don't render on connectedCallback()
|
|
109
104
|
|
|
110
105
|
// Add children before constructor code executes.
|
|
111
106
|
// PendingChildren is setup in NodeGroup.createNewComponent()
|
|
112
107
|
// TODO: Match named slots.
|
|
113
|
-
let ch =
|
|
108
|
+
let ch = Globals.pendingChildren.pop();
|
|
114
109
|
if (ch)
|
|
115
110
|
(this.querySelector('slot') || this).append(...ch);
|
|
116
111
|
|
|
117
112
|
/** @deprecated */
|
|
118
113
|
Object.defineProperty(this, 'html', {
|
|
119
114
|
set(html) {
|
|
120
|
-
rendered.add(this);
|
|
115
|
+
Globals.rendered.add(this);
|
|
121
116
|
if (typeof html === 'string') {
|
|
122
117
|
console.warn("Assigning to this.html without the r template prefix.")
|
|
123
118
|
this.innerHTML = html;
|
|
@@ -140,7 +135,7 @@ export default function createSolarite(extendsTag=null) {
|
|
|
140
135
|
/**
|
|
141
136
|
* Call render() only if it hasn't already been called. */
|
|
142
137
|
renderFirstTime() {
|
|
143
|
-
if (!rendered.has(this) && this.render)
|
|
138
|
+
if (!Globals.rendered.has(this) && this.render)
|
|
144
139
|
this.render();
|
|
145
140
|
}
|
|
146
141
|
|
|
@@ -148,8 +143,8 @@ export default function createSolarite(extendsTag=null) {
|
|
|
148
143
|
* Called automatically by the browser. */
|
|
149
144
|
connectedCallback() {
|
|
150
145
|
this.renderFirstTime();
|
|
151
|
-
if (!connected.has(this)) {
|
|
152
|
-
connected.add(this);
|
|
146
|
+
if (!Globals.connected.has(this)) {
|
|
147
|
+
Globals.connected.add(this);
|
|
153
148
|
this.onFirstConnect();
|
|
154
149
|
}
|
|
155
150
|
this.onConnect();
|
package/src/solarite/getArg.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* There are three ways to create an instance of a Solarite Component:
|
|
6
6
|
* 1. new ComponentName(); // direct class instantiation
|
|
7
|
-
* 2. this.html = r`<div><component-name></component-name></div>; // as a child of another
|
|
7
|
+
* 2. this.html = r`<div><component-name></component-name></div>; // as a child of another Component.
|
|
8
8
|
* 3. <body><component-name></component-name></body> // in the Document html.
|
|
9
9
|
*
|
|
10
10
|
* When created via #3, Solarite has no way to pass attributes as arguments to the constructor. So to make
|