solarite 0.5.2 → 0.7.1
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/LICENSE +21 -0
- package/dist/Solarite-debug.js +4847 -3878
- package/dist/Solarite.js +4583 -3626
- package/dist/Solarite.min.js +2 -4
- package/package.json +19 -6
- package/readme.md +58 -11
- package/src/Globals.js +54 -72
- package/src/HtmlParser.js +90 -90
- package/src/MultiValueMap.js +57 -105
- package/src/NodeGroup.js +624 -470
- package/src/Path.js +224 -211
- package/src/PathToAttribValue.js +401 -261
- package/src/PathToAttribs.js +113 -80
- package/src/PathToComment.js +7 -7
- package/src/PathToComponent.js +183 -188
- package/src/PathToEvent.js +76 -64
- package/src/PathToKey.js +19 -0
- package/src/PathToNodes.js +1053 -566
- package/src/RootNodeGroup.js +120 -8
- package/src/Shell.js +570 -348
- package/src/Solarite.d.ts +134 -113
- package/src/Solarite.js +243 -286
- package/src/Template.js +195 -274
- package/src/Util.js +353 -351
- package/src/assert.js +10 -10
- package/src/assignAttributes.js +63 -0
- package/src/delve.js +55 -43
- package/src/h.js +220 -138
- package/src/jsx-dev-runtime.d.ts +1 -0
- package/src/jsx-dev-runtime.js +5 -0
- package/src/jsx-runtime.d.ts +21 -0
- package/src/jsx-runtime.js +84 -0
- package/src/jsx.js +194 -0
- package/src/toEl.js +77 -82
- package/dist/udomdiff-license.txt +0 -18
- package/src/getArg.js +0 -137
- package/src/hash.js +0 -89
- package/src/udomdiff.js +0 -176
- package/src/unused/FastLookupArray.js +0 -54
- package/src/unused/Hashes.js +0 -339
- package/src/unused/InUse.test.js +0 -92
- package/src/unused/InUseMap.js +0 -98
- package/src/unused/LinkedList.js +0 -117
- package/src/unused/LinkedList.test.js +0 -115
- package/src/unused/Misc.js +0 -13
- package/src/unused/Perf.js +0 -47
- package/src/unused/TrackedArray.js +0 -54
- package/src/unused/WeakArray.js +0 -33
- package/src/watch.js +0 -543
package/src/Template.js
CHANGED
|
@@ -1,274 +1,195 @@
|
|
|
1
|
-
import assert from "./assert.js";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return this.
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// then it was appended above before children. Now just add the closing tag to the last html segment.
|
|
197
|
-
let lastIdx = htmlStrings.length - 1;
|
|
198
|
-
htmlStrings[lastIdx] += `</${tag}>`;
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
// Void element: ensure we emitted a trailing '>' segment
|
|
202
|
-
const pushedAny = htmlStrings.length > 0;
|
|
203
|
-
if (!pushedAny)
|
|
204
|
-
htmlStrings.push(open + '>');
|
|
205
|
-
else
|
|
206
|
-
htmlStrings.push('>');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Ensure invariant
|
|
210
|
-
//assert(htmlStrings.length === templateExprs.length + 1);
|
|
211
|
-
//console.log([htmlStrings, templateExprs])
|
|
212
|
-
return new Template(htmlStrings, templateExprs);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const selfClosingTags = new Set(['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr']);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Add child Templates that were already created via h() and Template.fromJsx()
|
|
222
|
-
* @param template {Template}
|
|
223
|
-
* @param html {string[]}
|
|
224
|
-
* @param exprs {any[]} */
|
|
225
|
-
const addChild = (template, html, exprs) => {
|
|
226
|
-
|
|
227
|
-
if (Array.isArray(template)) {
|
|
228
|
-
for (let c of template)
|
|
229
|
-
addChild(c, html, exprs);
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
let flatten = false;
|
|
233
|
-
if (template instanceof Template) {
|
|
234
|
-
// Heuristic to match tagged-template splitting:
|
|
235
|
-
// - Flatten if the child has expressions (so JSX can inline attribute/value placeholders like tagged literals would).
|
|
236
|
-
// - Also flatten void elements (e.g., <img>) so they inline like literals.
|
|
237
|
-
// - Otherwise, keep as a dynamic child placeholder to match cases where the tagged template used an expression child.
|
|
238
|
-
const childHasExprs = template.exprs.length > 0;
|
|
239
|
-
if (childHasExprs)
|
|
240
|
-
flatten = true;
|
|
241
|
-
else {
|
|
242
|
-
const m = (template.html[0] || '').match(/^<([a-zA-Z][\w:-]*)/);
|
|
243
|
-
const childTag = m ? m[1].toLowerCase() : '';
|
|
244
|
-
flatten = selfClosingTags.has(childTag);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (flatten) {
|
|
249
|
-
// Flatten/interleave into current segment to match tagged template splitting
|
|
250
|
-
html[html.length - 1] += template.html[0];
|
|
251
|
-
for (let i = 0; i < template.exprs.length; i++) {
|
|
252
|
-
exprs.push(template.exprs[i]);
|
|
253
|
-
html.push(template.html[i + 1] ?? '');
|
|
254
|
-
}
|
|
255
|
-
} else {
|
|
256
|
-
// Keep as dynamic child
|
|
257
|
-
exprs.push(template);
|
|
258
|
-
html.push('');
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* @typedef {Object} RenderOptions
|
|
266
|
-
* @property {boolean=} styles - Replace :host in style tags to scope them locally.
|
|
267
|
-
* @property {boolean=} scripts - Execute script tags.
|
|
268
|
-
* @property {boolean=} ids - Create references to elements with id or data-id attributes.
|
|
269
|
-
* @property {?boolean} render - Deprecated.
|
|
270
|
-
* Used only when options are given to a class super constructor inheriting from Solarite.
|
|
271
|
-
* True to call render() immediately in super constructor.
|
|
272
|
-
* False to automatically call render() at all.
|
|
273
|
-
* Undefined (default) to call render() when added to the DOM, unless already rendered.
|
|
274
|
-
*/
|
|
1
|
+
import assert from "./assert.js";
|
|
2
|
+
import Globals from "./Globals.js";
|
|
3
|
+
import RootNodeGroup from "./RootNodeGroup.js";
|
|
4
|
+
|
|
5
|
+
let lastObjectId = 1;
|
|
6
|
+
let objectIds = new WeakMap();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get a short string id unique to the given object, for use as a map key.
|
|
10
|
+
* @param obj {Object}
|
|
11
|
+
* @returns {string} */
|
|
12
|
+
function getObjectId(obj) {
|
|
13
|
+
let result = objectIds.get(obj);
|
|
14
|
+
if (result === undefined) {
|
|
15
|
+
result = '~@' + (lastObjectId++); // Unique 2-byte prefix so it can't collide with html-string keys.
|
|
16
|
+
objectIds.set(obj, result);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The html strings and evaluated expressions from an html tagged template.
|
|
23
|
+
* A unique Template is created for each item in a loop.
|
|
24
|
+
* Although the reference to the html strings is shared among templates. */
|
|
25
|
+
export default class Template {
|
|
26
|
+
|
|
27
|
+
/** @type {Expr[]} Evaulated expressions. Assigned by the constructor. */
|
|
28
|
+
'exprs' = undefined;
|
|
29
|
+
|
|
30
|
+
/** @type {string[]} Assigned by the constructor. */
|
|
31
|
+
'html' = undefined;
|
|
32
|
+
|
|
33
|
+
closeKey;
|
|
34
|
+
|
|
35
|
+
isText;
|
|
36
|
+
|
|
37
|
+
/** @type {*} List key for keyed diffing, set by JSX jsxTemplate()/jsxToTemplate() from a
|
|
38
|
+
* `key` prop. Tagged templates instead carry their key as an expr at Shell.keyIndex; the
|
|
39
|
+
* keyed reconciler (PathToNodes) reads whichever is present. */
|
|
40
|
+
key;
|
|
41
|
+
|
|
42
|
+
/** @type {boolean} True if created by the svg`` tag; the Shell parses the html in the SVG namespace. */
|
|
43
|
+
svgMode = false;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @param htmlStrings {string[]}
|
|
48
|
+
* @param exprs {*[]} */
|
|
49
|
+
constructor(htmlStrings=[''], exprs=[]) {
|
|
50
|
+
this.html = htmlStrings;
|
|
51
|
+
|
|
52
|
+
this.exprs = exprs;
|
|
53
|
+
|
|
54
|
+
//this.trace = new Error().stack.split(/\n/g)
|
|
55
|
+
|
|
56
|
+
//#IFDEV
|
|
57
|
+
assert(Array.isArray(htmlStrings))
|
|
58
|
+
assert(Array.isArray(exprs))
|
|
59
|
+
|
|
60
|
+
Object.defineProperty(this, 'debug', {
|
|
61
|
+
get() {
|
|
62
|
+
return JSON.stringify([this.html, this.exprs]);
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
//#ENDIF
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Render the main (root) template.
|
|
70
|
+
* @param el {?HTMLElement} Null if we're rendering to a standalone element.
|
|
71
|
+
* @param options {RenderOptions}
|
|
72
|
+
* @return {?DocumentFragment|HTMLElement} */
|
|
73
|
+
'render'(el=null, options={}) {
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
let ng = el && Globals.rootNodeGroups.get(el);
|
|
78
|
+
if (!ng) {
|
|
79
|
+
ng = new RootNodeGroup(this, null, el, options);
|
|
80
|
+
if (!el) // null if it's a standalone elment.
|
|
81
|
+
el = ng.getRootNode();
|
|
82
|
+
Globals.rootNodeGroups.set(el, ng); // All tests still pass if this is commented out!
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Make sure the expresion count matches match the Path "hole" count.
|
|
86
|
+
// This can happen if we try manually rendering one template to a NodeGroup that was created expecting a different template.
|
|
87
|
+
// These don't always have the same length, for example if one attribute has multiple expressions.
|
|
88
|
+
// if (ng.paths.length === 0 && this.exprs.length || ng.paths.length > this.exprs.length)
|
|
89
|
+
// throw new Error(
|
|
90
|
+
// `Solarite Error: Parent HTMLElement ${ng.template.html.join('${...}')} and ${ng.paths.length} \${value} ` +
|
|
91
|
+
// `placeholders can't accomodate a Template with ${this.exprs.length} values.`);
|
|
92
|
+
|
|
93
|
+
// Creating the root nodegroup also renders it.
|
|
94
|
+
// If we didn't just create it, we need to render it.
|
|
95
|
+
if (this.html?.length === 1 && !this.html[0]) // An empty string.
|
|
96
|
+
el.innerHTML = ''; // Fast path for empty component.
|
|
97
|
+
else
|
|
98
|
+
ng.applyExprs(this.exprs);
|
|
99
|
+
|
|
100
|
+
return el;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getCloseKey() {
|
|
104
|
+
if (this.closeKey===undefined) {
|
|
105
|
+
if (this.exprs.length)
|
|
106
|
+
this.closeKey = getObjectId(this.html);
|
|
107
|
+
else
|
|
108
|
+
this.closeKey = this.html[0];
|
|
109
|
+
}
|
|
110
|
+
// Use the joined html when debugging? But it breaks some tests.
|
|
111
|
+
//return '@'+this.html.join('|')
|
|
112
|
+
|
|
113
|
+
return this.closeKey;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Do two templates produce identical content?
|
|
121
|
+
* Compares expression values by identity, so no hashing or stringification is needed.
|
|
122
|
+
* @param a {Template}
|
|
123
|
+
* @param b {Template}
|
|
124
|
+
* @return {boolean} */
|
|
125
|
+
export function templatesSame(a, b) {
|
|
126
|
+
if (a.html === b.html && a.svgMode === b.svgMode) {
|
|
127
|
+
let ae = a.exprs, be = b.exprs;
|
|
128
|
+
for (let i=0; i<ae.length; i++)
|
|
129
|
+
if (!exprSame(ae[i], be[i]))
|
|
130
|
+
return false;
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Text and other single-string templates get a new html array each time, so compare by content.
|
|
135
|
+
if (a.isText === b.isText && !a.exprs.length && !b.exprs.length
|
|
136
|
+
&& a.html.length === 1 && b.html.length === 1 && a.svgMode === b.svgMode)
|
|
137
|
+
return a.html[0] === b.html[0];
|
|
138
|
+
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get a string that changes when any value inside obj changes, including deep mutations.
|
|
144
|
+
* Used by PathToComponent to compute the `changed` argument to component render() calls.
|
|
145
|
+
* Functions, Nodes, and repeated/circular objects are represented by identity ids.
|
|
146
|
+
* @param obj {*}
|
|
147
|
+
* @returns {string} */
|
|
148
|
+
export function getObjectHash(obj) {
|
|
149
|
+
const seen = new Set();
|
|
150
|
+
return JSON.stringify(obj, (key, value) => {
|
|
151
|
+
if (typeof value === 'function')
|
|
152
|
+
return getObjectId(value);
|
|
153
|
+
if (typeof value === 'object' && value !== null) {
|
|
154
|
+
if (value instanceof Node)
|
|
155
|
+
return getObjectId(value);
|
|
156
|
+
if (seen.has(value))
|
|
157
|
+
return getObjectId(value);
|
|
158
|
+
seen.add(value);
|
|
159
|
+
if (value instanceof Template)
|
|
160
|
+
return {html: getObjectId(value.html), exprs: value.exprs}; // Don't hash long html strings.
|
|
161
|
+
}
|
|
162
|
+
return value;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @return {boolean} */
|
|
168
|
+
export function exprSame(a, b) {
|
|
169
|
+
if (a === b)
|
|
170
|
+
return true;
|
|
171
|
+
if (Array.isArray(a)) {
|
|
172
|
+
if (!Array.isArray(b) || a.length !== b.length)
|
|
173
|
+
return false;
|
|
174
|
+
for (let i=0; i<a.length; i++)
|
|
175
|
+
if (!exprSame(a[i], b[i]))
|
|
176
|
+
return false;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
if (a instanceof Template && b instanceof Template)
|
|
180
|
+
return templatesSame(a, b);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @typedef {Object} RenderOptions
|
|
187
|
+
* @property {boolean=} styles - Replace :host in style tags to scope them locally.
|
|
188
|
+
* @property {boolean=} scripts - Execute script tags. Requires a CSP that allows unsafe-eval.
|
|
189
|
+
* @property {boolean=} ids - Create references to elements with id or data-id attributes.
|
|
190
|
+
* @property {?boolean} render - Deprecated.
|
|
191
|
+
* Used only when options are given to a class super constructor inheriting from Solarite.
|
|
192
|
+
* True to call render() immediately in super constructor.
|
|
193
|
+
* False to automatically call render() at all.
|
|
194
|
+
* Undefined (default) to call render() when added to the DOM, unless already rendered.
|
|
195
|
+
*/
|