solarite 0.3.0 → 0.3.2
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/dist/Solarite-debug.js +411 -524
- package/dist/Solarite.js +394 -507
- package/dist/Solarite.min.js +2 -2
- package/package.json +5 -6
- package/readme.md +2 -2
- package/src/{solarite/ExprPath.js → ExprPath.js} +43 -58
- package/src/{solarite/Globals.js → Globals.js} +4 -4
- package/src/{util/MultiValueMap.js → MultiValueMap.js} +1 -32
- package/src/{solarite/NodeGroup.js → NodeGroup.js} +30 -26
- package/src/{solarite/Shell.js → Shell.js} +8 -7
- package/src/Solarite.d.ts +62 -0
- package/src/{solarite/Solarite.js → Solarite.js} +13 -15
- package/src/{solarite/Template.js → Template.js} +1 -1
- package/src/{solarite/Util.js → Util.js} +49 -45
- package/src/{solarite/createSolarite.js → createSolarite.js} +12 -10
- package/src/{solarite/getArg.js → getArg.js} +24 -3
- package/src/{solarite/h.js → h.js} +4 -12
- package/src/unused/FastLookupArray.js +54 -0
- package/src/unused/Hashes.js +339 -0
- package/src/unused/InUse.test.js +92 -0
- package/src/unused/InUseMap.js +98 -0
- package/src/unused/LinkedList.js +117 -0
- package/src/unused/LinkedList.test.js +115 -0
- package/src/unused/Misc.js +13 -0
- package/src/unused/Perf.js +47 -0
- package/src/unused/TrackedArray.js +54 -0
- package/src/unused/WeakArray.js +33 -0
- package/src/{solarite/watch.js → watch.js} +4 -4
- package/src/util/Util.js +0 -101
- /package/src/{solarite/HtmlParser.js → HtmlParser.js} +0 -0
- /package/src/{util/Errors.js → assert.js} +0 -0
- /package/src/{util/delve.js → delve.js} +0 -0
- /package/src/{solarite/hash.js → hash.js} +0 -0
- /package/src/{solarite/udomdiff.js → udomdiff.js} +0 -0
package/dist/Solarite.js
CHANGED
|
@@ -1,98 +1,92 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* @property {function()} pause
|
|
6
|
-
* @property {function()} resume
|
|
7
|
-
* */
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
let lastObjectId = 1>>>0; // Is a 32-bit int faster to increment than JavaScript's Number, which is a 64-bit float?
|
|
4
|
+
let objectIds = new WeakMap();
|
|
8
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @param obj {Object|string|Node}
|
|
8
|
+
* @returns {string} */
|
|
9
|
+
function getObjectId(obj) {
|
|
10
|
+
// if (typeof obj === 'function')
|
|
11
|
+
// return obj.toString(); // This fails to detect when a function's bound variables changes.
|
|
12
|
+
|
|
13
|
+
let result = objectIds.get(obj);
|
|
14
|
+
if (!result) { // convert to string, store in result, then add 1 to lastObjectId.
|
|
15
|
+
result = '~\f' + (lastObjectId++); // We use a unique, 2-byte prefix to ensure it doesn't collide w/ strings not from getObjectId()
|
|
16
|
+
objectIds.set(obj, result);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
9
20
|
|
|
10
21
|
/**
|
|
11
|
-
*
|
|
12
|
-
|
|
22
|
+
* Control how JSON.stringify() handles Nodes and Functions.
|
|
23
|
+
* Normally, we'd pass a replacer() function argument to JSON.stringify() to handle Nodes and Functions.
|
|
24
|
+
* But that makes JSON.stringify() take twice as long to run.
|
|
25
|
+
* Adding a toJSON method globally on these object prototypes doesn't incur that performance penalty. */
|
|
26
|
+
let isHashing = true;
|
|
27
|
+
function toJSON() {
|
|
28
|
+
return isHashing ? getObjectId(this) : this
|
|
29
|
+
}
|
|
13
30
|
|
|
14
|
-
/**
|
|
15
|
-
* Create an array-like object that stores a group of callbacks.
|
|
16
|
-
* Supports all array functions and properties like push() and .length.
|
|
17
|
-
* Can be called directly.
|
|
18
|
-
*
|
|
19
|
-
* @param functions {function[]}
|
|
20
|
-
* @return {Callbacks|function}
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* var c = Util.callback();
|
|
24
|
-
* var f = () => console.log(3);
|
|
25
|
-
* c.push(f);
|
|
26
|
-
* c();
|
|
27
|
-
* c.remove(f);
|
|
28
|
-
* c();
|
|
29
|
-
*/
|
|
30
|
-
callback(...functions) {
|
|
31
|
-
var paused = false;
|
|
32
|
-
|
|
33
|
-
// Make it callable. When we call it, call all callbacks() with the given args.
|
|
34
|
-
let result = async function(...args) {
|
|
35
|
-
let result2 = [];
|
|
36
|
-
if (!paused)
|
|
37
|
-
for (let i=0; i<result.length; i++)
|
|
38
|
-
result2.push(result[i](...args));
|
|
39
|
-
return await Promise.all(result2);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Make it iterable.
|
|
43
|
-
result[Symbol.iterator] = function() {
|
|
44
|
-
let index = 0;
|
|
45
|
-
return {
|
|
46
|
-
next: () => index < result.length
|
|
47
|
-
? {value: result[index++], done: false}
|
|
48
|
-
: {done: true}
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
31
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (prop !== 'length' && prop !== 'constructor')
|
|
55
|
-
result[prop] = Array.prototype[prop];
|
|
56
|
-
|
|
57
|
-
result.l = 0; // Internal length
|
|
58
|
-
Object.defineProperty(result, 'length', {
|
|
59
|
-
get() { return result.l },
|
|
60
|
-
set(val) { result.l = val;}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Add the remove() function.
|
|
64
|
-
result.remove = func => {
|
|
65
|
-
let idx = result.findIndex(item => item === func);
|
|
66
|
-
if (idx !== -1)
|
|
67
|
-
result.splice(idx, 1);
|
|
68
|
-
};
|
|
69
|
-
result.pause = () => paused = true;
|
|
32
|
+
// Node.prototype.toJSON = toJSON;
|
|
33
|
+
// Function.prototype.toJSON = toJSON;
|
|
70
34
|
|
|
71
|
-
result.resume = () => paused = false;
|
|
72
35
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Get a string that uniquely maps to the values of the given object.
|
|
38
|
+
* If a value in obj changes, calling getObjectHash(obj) will then return a different hash.
|
|
39
|
+
* This is used by NodeGroupManager to create a hash that represents the current values of a NodeGroup.
|
|
40
|
+
*
|
|
41
|
+
* Relies on the Node and Function prototypes being overridden above.
|
|
42
|
+
*
|
|
43
|
+
* Note that passing an integer may collide with the number we get from hashing an object.
|
|
44
|
+
* But we don't handle that case because we need max performance and Solarite never passes integers to this function.
|
|
45
|
+
*
|
|
46
|
+
* @param obj {*}
|
|
47
|
+
* @returns {string} */
|
|
48
|
+
function getObjectHash(obj) {
|
|
76
49
|
|
|
77
|
-
|
|
78
|
-
|
|
50
|
+
// Sometimes these get unassigned by Chrome and Brave 119, as well as Firefox, seemingly randomly!
|
|
51
|
+
// The same tests sometimes pass, sometimes fail, even after browser and OS restarts.
|
|
52
|
+
// So we check the assignments on every run of getObjectHash()
|
|
53
|
+
if (Node.prototype.toJSON !== toJSON) {
|
|
54
|
+
Node.prototype.toJSON = toJSON;
|
|
55
|
+
if (Function.prototype.toJSON !== toJSON) // Will it only unmap one but not the other?
|
|
56
|
+
Function.prototype.toJSON = toJSON;
|
|
57
|
+
}
|
|
79
58
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
59
|
+
let result;
|
|
60
|
+
isHashing = true;
|
|
61
|
+
try {
|
|
62
|
+
result = JSON.stringify(obj);
|
|
63
|
+
}
|
|
64
|
+
catch(e) {
|
|
65
|
+
result = getObjectHashCircular(obj);
|
|
66
|
+
}
|
|
67
|
+
isHashing = false;
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Slower hashing method that supports.
|
|
73
|
+
* @param obj
|
|
74
|
+
* @returns {string} */
|
|
75
|
+
function getObjectHashCircular(obj) {
|
|
76
|
+
|
|
77
|
+
//console.log('circular')
|
|
78
|
+
// Slower version that handles circular references.
|
|
79
|
+
// Just adding any callback at all, even one that just returns the value, makes JSON.stringify() twice as slow.
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
return JSON.stringify(obj, (key, value) => {
|
|
82
|
+
if (typeof value === 'object' && value !== null) {
|
|
83
|
+
if (seen.has(value))
|
|
84
|
+
return getObjectId(value);
|
|
85
|
+
seen.add(value);
|
|
91
86
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
87
|
+
return value;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
96
90
|
|
|
97
91
|
var Globals;
|
|
98
92
|
|
|
@@ -119,15 +113,15 @@ function reset() {
|
|
|
119
113
|
div: document.createElement("div"),
|
|
120
114
|
|
|
121
115
|
/**
|
|
122
|
-
* @type {
|
|
116
|
+
* @type {Record<string, Class<Node>>} A map from built-in tag names to the constructors that create them. */
|
|
123
117
|
elementClasses: {},
|
|
124
118
|
|
|
125
|
-
/** @type {
|
|
119
|
+
/** @type {Record<string, boolean>} Key is tag-name.propName. Value is whether it's an attribute.*/
|
|
126
120
|
htmlProps: {},
|
|
127
121
|
|
|
128
122
|
/**
|
|
129
123
|
* Used by ExprPath.applyEventAttrib()
|
|
130
|
-
* @type {WeakMap<Node,
|
|
124
|
+
* @type {WeakMap<Node, Record<eventName:string, [original:function, bound:function, args:*[]]>>} */
|
|
131
125
|
nodeEvents: new WeakMap(),
|
|
132
126
|
|
|
133
127
|
/**
|
|
@@ -162,7 +156,7 @@ function reset() {
|
|
|
162
156
|
* A map of individual untagged strings to their Templates.
|
|
163
157
|
* This way we don't keep creating new Templates for the same string when re-rendering.
|
|
164
158
|
* This is used by ExprPath.applyExactNodes()
|
|
165
|
-
* @type {
|
|
159
|
+
* @type {Record<string, Template>} */
|
|
166
160
|
//stringTemplates: {},
|
|
167
161
|
|
|
168
162
|
reset,
|
|
@@ -221,6 +215,21 @@ let d = {};
|
|
|
221
215
|
|
|
222
216
|
let Util = {
|
|
223
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Returns true if they're the same.
|
|
220
|
+
* @param a
|
|
221
|
+
* @param b
|
|
222
|
+
* @returns {boolean} */
|
|
223
|
+
arraySame(a, b) {
|
|
224
|
+
let aLength = a.length;
|
|
225
|
+
if (aLength !== b.length)
|
|
226
|
+
return false;
|
|
227
|
+
for (let i=0; i<aLength; i++)
|
|
228
|
+
if (a[i] !== b[i])
|
|
229
|
+
return false;
|
|
230
|
+
return true; // the same.
|
|
231
|
+
},
|
|
232
|
+
|
|
224
233
|
bindId(root, el) {
|
|
225
234
|
let id = el.getAttribute('data-id') || el.getAttribute('id');
|
|
226
235
|
if (id) { // If something hasn't removed the id.
|
|
@@ -261,7 +270,6 @@ let Util = {
|
|
|
261
270
|
}
|
|
262
271
|
},
|
|
263
272
|
|
|
264
|
-
|
|
265
273
|
/**
|
|
266
274
|
* Convert a Proper Case name to a name with dashes.
|
|
267
275
|
* Dashes will be placed between letters and numbers.
|
|
@@ -329,17 +337,17 @@ let Util = {
|
|
|
329
337
|
* for (const item of flatten(complexArray))
|
|
330
338
|
* console.log(item); // Outputs: 1, 2, 3, 4, 5, 6, { a: 'object' }, 7, 8, 9
|
|
331
339
|
*/
|
|
332
|
-
*flatten(value) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
},
|
|
340
|
+
// *flatten(value) {
|
|
341
|
+
// if (Array.isArray(value)) {
|
|
342
|
+
// for (const item of value) {
|
|
343
|
+
// yield* Util.flatten(item); // Recursively flatten arrays
|
|
344
|
+
// }
|
|
345
|
+
// } else if (typeof value === 'function') {
|
|
346
|
+
// const result = value();
|
|
347
|
+
// yield* Util.flatten(result); // Recursively flatten the result of a function
|
|
348
|
+
// } else
|
|
349
|
+
// yield value; // Yield primitive values as is
|
|
350
|
+
// },
|
|
343
351
|
|
|
344
352
|
/**
|
|
345
353
|
* Get the value of an input as the most appropriate JavaScript type.
|
|
@@ -361,6 +369,10 @@ let Util = {
|
|
|
361
369
|
return node.value; // String
|
|
362
370
|
},
|
|
363
371
|
|
|
372
|
+
isEvent(attrName) {
|
|
373
|
+
return attrName.startsWith('on') && attrName in Globals$1.div;
|
|
374
|
+
},
|
|
375
|
+
|
|
364
376
|
/**
|
|
365
377
|
* @param el {HTMLElement}
|
|
366
378
|
* @param prop {string}
|
|
@@ -400,9 +412,10 @@ let Util = {
|
|
|
400
412
|
return val === undefined || val === false || val === null;
|
|
401
413
|
},
|
|
402
414
|
|
|
415
|
+
/*
|
|
403
416
|
isPrimitive(val) {
|
|
404
417
|
return typeof val === 'string' || typeof val === 'number'
|
|
405
|
-
}
|
|
418
|
+
},*/
|
|
406
419
|
|
|
407
420
|
/**
|
|
408
421
|
* If val is a function, evaluate it recursively until the result is not a function.
|
|
@@ -420,6 +433,22 @@ let Util = {
|
|
|
420
433
|
return val;
|
|
421
434
|
},
|
|
422
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Use an array as the value of a map, appending to it when we add.
|
|
438
|
+
* Used by watch.js.
|
|
439
|
+
* @param map {Map|WeakMap|Object}
|
|
440
|
+
* @param key
|
|
441
|
+
* @param value */
|
|
442
|
+
mapArrayAdd(map, key, value) {
|
|
443
|
+
let result = map.get(key);
|
|
444
|
+
if (!result) {
|
|
445
|
+
result = [value];
|
|
446
|
+
map.set(key, result);
|
|
447
|
+
}
|
|
448
|
+
else
|
|
449
|
+
result.push(value);
|
|
450
|
+
},
|
|
451
|
+
|
|
423
452
|
/**
|
|
424
453
|
* Remove nodes from the beginning and end that are not:
|
|
425
454
|
* 1. Elements.
|
|
@@ -436,288 +465,24 @@ let Util = {
|
|
|
436
465
|
|
|
437
466
|
// Trim from the start
|
|
438
467
|
while (result.length > 0 && shouldTrimNode(result[0]))
|
|
439
|
-
result.shift();
|
|
440
|
-
|
|
441
|
-
// Trim from the end
|
|
442
|
-
while (result.length > 0 && shouldTrimNode(result[result.length - 1]))
|
|
443
|
-
result.pop();
|
|
444
|
-
|
|
445
|
-
return result;
|
|
446
|
-
}
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
let isEvent = attrName => attrName.startsWith('on') && attrName in Globals$1.div;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Returns true if they're the same.
|
|
459
|
-
* @param a
|
|
460
|
-
* @param b
|
|
461
|
-
* @returns {boolean} */
|
|
462
|
-
function arraySame(a, b) {
|
|
463
|
-
let aLength = a.length;
|
|
464
|
-
if (aLength !== b.length)
|
|
465
|
-
return false;
|
|
466
|
-
for (let i=0; i<aLength; i++)
|
|
467
|
-
if (a[i] !== b[i])
|
|
468
|
-
return false;
|
|
469
|
-
return true; // the same.
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
// For debugging only
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
function defineClass(Class, tagName, extendsTag) {
|
|
480
|
-
if (!customElements[getName](Class)) { // If not previously defined.
|
|
481
|
-
tagName = tagName || Util.camelToDashes(Class.name);
|
|
482
|
-
if (!tagName.includes('-'))
|
|
483
|
-
tagName += '-element';
|
|
484
|
-
|
|
485
|
-
let options = null;
|
|
486
|
-
if (extendsTag)
|
|
487
|
-
options = {extends: extendsTag};
|
|
488
|
-
|
|
489
|
-
customElements[define](tagName, Class, options);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Create a version of the Solarite class that extends from the given tag name.
|
|
495
|
-
* Reasons to inherit from this instead of HTMLElement. None of these are all that useful.
|
|
496
|
-
* 1. customElements.define() is called automatically when you create the first instance.
|
|
497
|
-
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
498
|
-
* 3. Child elements are added before constructor is called. But they're also passed to the constructor. (deprecated?)
|
|
499
|
-
* 4. We can use this.html = r`...` to set html. (deprecated)
|
|
500
|
-
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
501
|
-
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
502
|
-
* 6. Can we extend from other element types like TR?
|
|
503
|
-
* 7. Shows default text if render() function isn't defined.
|
|
504
|
-
*
|
|
505
|
-
* Advantages to inheriting from HTMLElement
|
|
506
|
-
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
507
|
-
* 2. We can inherit from things like HTMLTableRowElement directly.
|
|
508
|
-
* 3. There's less magic, since everyone is familiar with defining custom elements.
|
|
509
|
-
*
|
|
510
|
-
* @param extendsTag {?string}
|
|
511
|
-
* @return {Class} */
|
|
512
|
-
function createSolarite(extendsTag=null) {
|
|
513
|
-
|
|
514
|
-
let BaseClass = HTMLElement;
|
|
515
|
-
if (extendsTag && !extendsTag.includes('-')) {
|
|
516
|
-
extendsTag = extendsTag.toLowerCase();
|
|
517
|
-
|
|
518
|
-
BaseClass = Globals$1.elementClasses[extendsTag];
|
|
519
|
-
if (!BaseClass) { // TODO: Use Cache
|
|
520
|
-
BaseClass = document.createElement(extendsTag).constructor;
|
|
521
|
-
Globals$1.elementClasses[extendsTag] = BaseClass;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
/**
|
|
526
|
-
* Intercept the construct call to auto-define the class before the constructor is called.
|
|
527
|
-
* @type {HTMLElement} */
|
|
528
|
-
let HTMLElementAutoDefine = new Proxy(BaseClass, {
|
|
529
|
-
construct(Parent, args, Class) {
|
|
530
|
-
defineClass(Class, null, extendsTag);
|
|
531
|
-
|
|
532
|
-
// This is a good place to manipulate any args before they're sent to the constructor.
|
|
533
|
-
// Such as loading them from attributes, if I could find a way to do so.
|
|
534
|
-
|
|
535
|
-
// This line is equivalent the to super() call.
|
|
536
|
-
return Reflect.construct(Parent, args, Class);
|
|
537
|
-
}
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
return class Solarite extends HTMLElementAutoDefine {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* TODO: Make these standalone functions.
|
|
545
|
-
* Callbacks.
|
|
546
|
-
* Use onConnect.push(() => ...); to add new callbacks. */
|
|
547
|
-
onConnect = Util$1.callback();
|
|
548
|
-
|
|
549
|
-
onFirstConnect = Util$1.callback();
|
|
550
|
-
onDisconnect = Util$1.callback();
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* @param options {RenderOptions} */
|
|
554
|
-
constructor(options={}) {
|
|
555
|
-
super();
|
|
556
|
-
|
|
557
|
-
// TODO: Is options.render ever used?
|
|
558
|
-
if (options.render===true)
|
|
559
|
-
this.render();
|
|
560
|
-
|
|
561
|
-
else if (options.render===false)
|
|
562
|
-
Globals$1.rendered.add(this); // Don't render on connectedCallback()
|
|
563
|
-
|
|
564
|
-
// Add slot children before constructor code executes.
|
|
565
|
-
// This breaks the styleStaticNested test.
|
|
566
|
-
// PendingChildren is setup in NodeGroup.createNewComponent()
|
|
567
|
-
// TODO: Match named slots.
|
|
568
|
-
//let ch = Globals.pendingChildren.pop();
|
|
569
|
-
//if (ch) // TODO: how could there be a slot before render is called?
|
|
570
|
-
// (this.querySelector('slot') || this).append(...ch);
|
|
571
|
-
|
|
572
|
-
/** @deprecated
|
|
573
|
-
Object.defineProperty(this, 'html', {
|
|
574
|
-
set(html) {
|
|
575
|
-
Globals.rendered.add(this);
|
|
576
|
-
if (typeof html === 'string') {
|
|
577
|
-
console.warn("Assigning to this.html without the r template prefix.")
|
|
578
|
-
this.innerHTML = html;
|
|
579
|
-
}
|
|
580
|
-
else
|
|
581
|
-
this.modifications = r(this, html, options);
|
|
582
|
-
}
|
|
583
|
-
})*/
|
|
584
|
-
|
|
585
|
-
/*
|
|
586
|
-
let pthis = new Proxy(this, {
|
|
587
|
-
get(obj, prop) {
|
|
588
|
-
return Reflect.get(obj, prop)
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
this.render = this.render.bind(pthis);
|
|
592
|
-
*/
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* Call render() only if it hasn't already been called. */
|
|
597
|
-
renderFirstTime() {
|
|
598
|
-
if (!Globals$1.rendered.has(this) && this.render)
|
|
599
|
-
this.render();
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* Called automatically by the browser. */
|
|
604
|
-
connectedCallback() {
|
|
605
|
-
this.renderFirstTime();
|
|
606
|
-
if (!Globals$1.connected.has(this)) {
|
|
607
|
-
Globals$1.connected.add(this);
|
|
608
|
-
this.onFirstConnect();
|
|
609
|
-
}
|
|
610
|
-
this.onConnect();
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
disconnectedCallback() {
|
|
614
|
-
this.onDisconnect();
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
static define(tagName=null) {
|
|
619
|
-
defineClass(this, tagName, extendsTag);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// Trick to prevent minifier from renaming this method.
|
|
625
|
-
let define = 'define';
|
|
626
|
-
let getName = 'getName';
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
let lastObjectId = 1>>>0; // Is a 32-bit int faster to increment than JavaScript's Number, which is a 64-bit float?
|
|
631
|
-
let objectIds = new WeakMap();
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* @param obj {Object|string|Node}
|
|
635
|
-
* @returns {string} */
|
|
636
|
-
function getObjectId(obj) {
|
|
637
|
-
// if (typeof obj === 'function')
|
|
638
|
-
// return obj.toString(); // This fails to detect when a function's bound variables changes.
|
|
639
|
-
|
|
640
|
-
let result = objectIds.get(obj);
|
|
641
|
-
if (!result) { // convert to string, store in result, then add 1 to lastObjectId.
|
|
642
|
-
result = '~\f' + (lastObjectId++); // We use a unique, 2-byte prefix to ensure it doesn't collide w/ strings not from getObjectId()
|
|
643
|
-
objectIds.set(obj, result);
|
|
644
|
-
}
|
|
645
|
-
return result;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* Control how JSON.stringify() handles Nodes and Functions.
|
|
650
|
-
* Normally, we'd pass a replacer() function argument to JSON.stringify() to handle Nodes and Functions.
|
|
651
|
-
* But that makes JSON.stringify() take twice as long to run.
|
|
652
|
-
* Adding a toJSON method globally on these object prototypes doesn't incur that performance penalty. */
|
|
653
|
-
let isHashing = true;
|
|
654
|
-
function toJSON() {
|
|
655
|
-
return isHashing ? getObjectId(this) : this
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
// Node.prototype.toJSON = toJSON;
|
|
660
|
-
// Function.prototype.toJSON = toJSON;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Get a string that uniquely maps to the values of the given object.
|
|
665
|
-
* If a value in obj changes, calling getObjectHash(obj) will then return a different hash.
|
|
666
|
-
* This is used by NodeGroupManager to create a hash that represents the current values of a NodeGroup.
|
|
667
|
-
*
|
|
668
|
-
* Relies on the Node and Function prototypes being overridden above.
|
|
669
|
-
*
|
|
670
|
-
* Note that passing an integer may collide with the number we get from hashing an object.
|
|
671
|
-
* But we don't handle that case because we need max performance and Solarite never passes integers to this function.
|
|
672
|
-
*
|
|
673
|
-
* @param obj {*}
|
|
674
|
-
* @returns {string} */
|
|
675
|
-
function getObjectHash(obj) {
|
|
676
|
-
|
|
677
|
-
// Sometimes these get unassigned by Chrome and Brave 119, as well as Firefox, seemingly randomly!
|
|
678
|
-
// The same tests sometimes pass, sometimes fail, even after browser and OS restarts.
|
|
679
|
-
// So we check the assignments on every run of getObjectHash()
|
|
680
|
-
if (Node.prototype.toJSON !== toJSON) {
|
|
681
|
-
Node.prototype.toJSON = toJSON;
|
|
682
|
-
if (Function.prototype.toJSON !== toJSON) // Will it only unmap one but not the other?
|
|
683
|
-
Function.prototype.toJSON = toJSON;
|
|
684
|
-
}
|
|
468
|
+
result.shift();
|
|
685
469
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
catch(e) {
|
|
692
|
-
result = getObjectHashCircular(obj);
|
|
470
|
+
// Trim from the end
|
|
471
|
+
while (result.length > 0 && shouldTrimNode(result[result.length - 1]))
|
|
472
|
+
result.pop();
|
|
473
|
+
|
|
474
|
+
return result;
|
|
693
475
|
}
|
|
694
|
-
|
|
695
|
-
return result;
|
|
696
|
-
}
|
|
476
|
+
};
|
|
697
477
|
|
|
698
|
-
/**
|
|
699
|
-
* Slower hashing method that supports.
|
|
700
|
-
* @param obj
|
|
701
|
-
* @returns {string} */
|
|
702
|
-
function getObjectHashCircular(obj) {
|
|
703
478
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const seen = new Set();
|
|
708
|
-
return JSON.stringify(obj, (key, value) => {
|
|
709
|
-
if (typeof value === 'object' && value !== null) {
|
|
710
|
-
if (seen.has(value))
|
|
711
|
-
return getObjectId(value);
|
|
712
|
-
seen.add(value);
|
|
713
|
-
}
|
|
714
|
-
return value;
|
|
715
|
-
});
|
|
716
|
-
}
|
|
479
|
+
|
|
480
|
+
// For debugging only
|
|
481
|
+
|
|
717
482
|
|
|
718
483
|
class MultiValueMap {
|
|
719
484
|
|
|
720
|
-
/** @type {
|
|
485
|
+
/** @type {Record<string, Set>} */
|
|
721
486
|
data = {};
|
|
722
487
|
|
|
723
488
|
// Set a new value for a key
|
|
@@ -811,37 +576,6 @@ class MultiValueMap {
|
|
|
811
576
|
return result;
|
|
812
577
|
}
|
|
813
578
|
|
|
814
|
-
|
|
815
|
-
/**
|
|
816
|
-
* Try to delete an item that matches the key and the isPreferred function.
|
|
817
|
-
* if not the latter, just delete any item that matches the key.
|
|
818
|
-
* @param key {string}
|
|
819
|
-
* @returns {*|undefined} The deleted item. */
|
|
820
|
-
deletePreferred(key, parent) {
|
|
821
|
-
let result;
|
|
822
|
-
let data = this.data;
|
|
823
|
-
let set = data[key];
|
|
824
|
-
if (!set)
|
|
825
|
-
return undefined;
|
|
826
|
-
|
|
827
|
-
for (let val of set) {
|
|
828
|
-
if (val?.parentNode === parent) {
|
|
829
|
-
set.delete(val);
|
|
830
|
-
result = val;
|
|
831
|
-
break;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
if (!result) {
|
|
835
|
-
[result] = set;
|
|
836
|
-
set.delete(result);
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
if (set.size === 0)
|
|
840
|
-
delete data[key];
|
|
841
|
-
|
|
842
|
-
return result;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
579
|
hasValue(val) {
|
|
846
580
|
let data = this.data;
|
|
847
581
|
let names = [];
|
|
@@ -1237,7 +971,7 @@ class ExprPath {
|
|
|
1237
971
|
|
|
1238
972
|
|
|
1239
973
|
// This pre-check makes it a few percent faster?
|
|
1240
|
-
let same = arraySame(oldNodes, newNodes);
|
|
974
|
+
let same = Util.arraySame(oldNodes, newNodes);
|
|
1241
975
|
if (!same) {
|
|
1242
976
|
|
|
1243
977
|
path.nodesCache = newNodes; // Replaces value set by path.getNodes()
|
|
@@ -1259,6 +993,17 @@ class ExprPath {
|
|
|
1259
993
|
for (let ng of oldNodeGroups)
|
|
1260
994
|
if (!ng.startNode.parentNode)
|
|
1261
995
|
ng.removeAndSaveOrphans();
|
|
996
|
+
|
|
997
|
+
// Instantiate components created within ${...} expressions.
|
|
998
|
+
// Embedded style tags are handled elsewhere, but where?
|
|
999
|
+
for (let el of newNodes) {
|
|
1000
|
+
if (el instanceof HTMLElement) {
|
|
1001
|
+
if (el.hasAttribute('solarite-placeholder'))
|
|
1002
|
+
this.parentNg.instantiateComponent(el);
|
|
1003
|
+
for (let child of el.querySelectorAll('[solarite-placeholder]'))
|
|
1004
|
+
this.parentNg.instantiateComponent(child);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1262
1007
|
}
|
|
1263
1008
|
|
|
1264
1009
|
|
|
@@ -1433,50 +1178,10 @@ class ExprPath {
|
|
|
1433
1178
|
// Arrays and functions.
|
|
1434
1179
|
// I tried iterating over the result of a generator function to avoid this recursion and simplify the code,
|
|
1435
1180
|
// but that consistently made the js-framework-benchmarks a few percentage points slower.
|
|
1436
|
-
else
|
|
1181
|
+
else
|
|
1437
1182
|
this.exprToTemplates(expr, template => {
|
|
1438
1183
|
this.applyExactNodes(template, newNodes, secondPass);
|
|
1439
1184
|
});
|
|
1440
|
-
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
// Old version
|
|
1444
|
-
/*else if (Array.isArray(expr))
|
|
1445
|
-
for (let subExpr of expr)
|
|
1446
|
-
this.applyExactNodes(subExpr, newNodes, secondPass);
|
|
1447
|
-
|
|
1448
|
-
else if (typeof expr === 'function') {
|
|
1449
|
-
// TODO: One ExprPath can have multiple expr functions.
|
|
1450
|
-
// But if using it as a watch, it should only have one at the top level.
|
|
1451
|
-
// So maybe this is ok.
|
|
1452
|
-
Globals.currentExprPath = this; // Used by watch()
|
|
1453
|
-
|
|
1454
|
-
this.watchFunction = expr; // TODO: Only do this if it's a top level function.
|
|
1455
|
-
let result = expr(); // As expr accesses watched variables, watch() uses Globals.currentExprPath to mark where those watched variables are being used.
|
|
1456
|
-
Globals.currentExprPath = null;
|
|
1457
|
-
|
|
1458
|
-
this.applyExactNodes(result, newNodes, secondPass);
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
// String
|
|
1462
|
-
else {
|
|
1463
|
-
// Convert expression to a string.
|
|
1464
|
-
let stringExpr = expr;
|
|
1465
|
-
if (expr === undefined || expr === false || expr === null) // Util.isFalsy()
|
|
1466
|
-
stringExpr = '';
|
|
1467
|
-
else if (typeof expr !== 'string')
|
|
1468
|
-
stringExpr = expr + '';
|
|
1469
|
-
|
|
1470
|
-
// Get the same Template for the same string each time.
|
|
1471
|
-
let template = Globals.stringTemplates[stringExpr];
|
|
1472
|
-
if (!template) {
|
|
1473
|
-
template = new Template([stringExpr], []);
|
|
1474
|
-
Globals.stringTemplates[stringExpr] = template;
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
// Recurse.
|
|
1478
|
-
this.applyExactNodes(template, newNodes, secondPass);
|
|
1479
|
-
}*/
|
|
1480
1185
|
}
|
|
1481
1186
|
|
|
1482
1187
|
applyMultipleAttribs(node, expr) {
|
|
@@ -1496,16 +1201,30 @@ class ExprPath {
|
|
|
1496
1201
|
Globals$1.currentExprPath = null;
|
|
1497
1202
|
}
|
|
1498
1203
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1204
|
+
// Attribute as name: value object.
|
|
1205
|
+
if (typeof expr === 'object') {
|
|
1206
|
+
for (let name in expr) {
|
|
1207
|
+
let value = expr[name];
|
|
1208
|
+
if (value === undefined || value === false || value === null)
|
|
1209
|
+
continue;
|
|
1210
|
+
node.setAttribute(name, value);
|
|
1211
|
+
this.attrNames.add(name);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1503
1214
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1215
|
+
// Attributes as string
|
|
1216
|
+
else {
|
|
1217
|
+
let attrs = (expr + '') // Split string into multiple attributes.
|
|
1218
|
+
.split(/([\w-]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s]+))/g)
|
|
1219
|
+
.map(text => text.trim())
|
|
1220
|
+
.filter(text => text.length);
|
|
1221
|
+
|
|
1222
|
+
for (let attr of attrs) {
|
|
1223
|
+
let [name, value] = attr.split(/\s*=\s*/); // split on first equals.
|
|
1224
|
+
value = (value || '').replace(/^(['"])(.*)\1$/, '$2'); // trim value quotes if they match.
|
|
1225
|
+
node.setAttribute(name, value);
|
|
1226
|
+
this.attrNames.add(name);
|
|
1227
|
+
}
|
|
1509
1228
|
}
|
|
1510
1229
|
}
|
|
1511
1230
|
|
|
@@ -1616,7 +1335,7 @@ class ExprPath {
|
|
|
1616
1335
|
// Copies the attribute to the property when the input event fires.
|
|
1617
1336
|
// value=${[this, 'value]'}
|
|
1618
1337
|
// checked=${[this, 'isAgree']}
|
|
1619
|
-
// This same logic is in NodeGroup.
|
|
1338
|
+
// This same logic is in NodeGroup.instantiateComponent() for components.
|
|
1620
1339
|
if (Util.isPath(expr)) {
|
|
1621
1340
|
let [obj, path] = [expr[0], expr.slice(1)];
|
|
1622
1341
|
|
|
@@ -2156,7 +1875,7 @@ class Shell {
|
|
|
2156
1875
|
/** @type {int[][]} Array of paths. Used by activateEmbeds() to quickly find components. */
|
|
2157
1876
|
staticComponents = [];
|
|
2158
1877
|
|
|
2159
|
-
/** @type {{path:int[], attribs:
|
|
1878
|
+
/** @type {{path:int[], attribs:Record<string, string>}[]} */
|
|
2160
1879
|
//componentAttribs = [];
|
|
2161
1880
|
|
|
2162
1881
|
|
|
@@ -2215,7 +1934,7 @@ class Shell {
|
|
|
2215
1934
|
let parts = attr.value.split(/[\ue000-\uf8ff]/g);
|
|
2216
1935
|
if (parts.length > 1) {
|
|
2217
1936
|
let nonEmptyParts = (parts.length === 2 && !parts[0].length && !parts[1].length) ? null : parts;
|
|
2218
|
-
let type = isEvent(attr.name) ? ExprPathType.Event : ExprPathType.AttribValue;
|
|
1937
|
+
let type = Util.isEvent(attr.name) ? ExprPathType.Event : ExprPathType.AttribValue;
|
|
2219
1938
|
|
|
2220
1939
|
this.paths.push(new ExprPath(null, node, type, attr.name, nonEmptyParts));
|
|
2221
1940
|
placeholdersUsed += parts.length - 1;
|
|
@@ -2343,11 +2062,12 @@ class Shell {
|
|
|
2343
2062
|
function addToken(token, context) {
|
|
2344
2063
|
|
|
2345
2064
|
if (context === HtmlParser.Tag) {
|
|
2346
|
-
// Find Solarite Components tags and append -solarite-placeholder to their tag names
|
|
2065
|
+
// Find Solarite Components tags and append -solarite-placeholder to their tag names
|
|
2066
|
+
// and give them a solarite-placeholder attribute so we can easily find them later.
|
|
2347
2067
|
// This way we can gather their constructor arguments and their children before we call their constructor.
|
|
2348
|
-
// Later, NodeGroup.
|
|
2068
|
+
// Later, NodeGroup.instantiateComponent() will replace them with the real components.
|
|
2349
2069
|
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
2350
|
-
token = token.replace(/^<\/?[a-z][a-z0-9]*-[a-z0-9-]+/i, match => match + '-solarite-placeholder');
|
|
2070
|
+
token = token.replace(/^<\/?[a-z][a-z0-9]*-[a-z0-9-]+/i, match => match + '-solarite-placeholder solarite-placeholder');
|
|
2351
2071
|
}
|
|
2352
2072
|
tokens.push(token);
|
|
2353
2073
|
}
|
|
@@ -2510,7 +2230,7 @@ class NodeGroup {
|
|
|
2510
2230
|
// Apply exprs
|
|
2511
2231
|
this.applyExprs(template.exprs);
|
|
2512
2232
|
|
|
2513
|
-
this.
|
|
2233
|
+
this.instantiateStaticComponents(staticComponents);
|
|
2514
2234
|
}
|
|
2515
2235
|
else if (shell)
|
|
2516
2236
|
this.activateEmbeds(fragment, shell);
|
|
@@ -2603,7 +2323,7 @@ class NodeGroup {
|
|
|
2603
2323
|
// Think of having two adjacent components.
|
|
2604
2324
|
// But the dynamicAttribsAdjacet test already passes.
|
|
2605
2325
|
|
|
2606
|
-
// If a component:
|
|
2326
|
+
// If expr is an attribute in a component:
|
|
2607
2327
|
// 1. Instantiate it if it hasn't already been, sending all expr's to its constructor.
|
|
2608
2328
|
// 2. Otherwise send them to its render function.
|
|
2609
2329
|
// Components with no expressions as attributes are instead activated in activateEmbeds().
|
|
@@ -2640,6 +2360,8 @@ class NodeGroup {
|
|
|
2640
2360
|
// TODO: Only do this if we have ExprPaths within styles?
|
|
2641
2361
|
this.updateStyles();
|
|
2642
2362
|
|
|
2363
|
+
|
|
2364
|
+
|
|
2643
2365
|
// Invalidate the nodes cache because we just changed it.
|
|
2644
2366
|
this.nodesCache = null;
|
|
2645
2367
|
|
|
@@ -2662,23 +2384,25 @@ class NodeGroup {
|
|
|
2662
2384
|
// then we could re-use the hash and logic from NodeManager?
|
|
2663
2385
|
let newHash = getObjectHash(props);
|
|
2664
2386
|
|
|
2665
|
-
let isPreHtmlElement = el.
|
|
2387
|
+
let isPreHtmlElement = el.hasAttribute('solarite-placeholder');
|
|
2666
2388
|
let isPreIsElement = el.hasAttribute('_is');
|
|
2667
2389
|
|
|
2668
2390
|
|
|
2669
2391
|
// Instantiate a placeholder.
|
|
2670
2392
|
if (isPreHtmlElement || isPreIsElement)
|
|
2671
|
-
el = this.
|
|
2393
|
+
el = this.instantiateComponent(el, isPreHtmlElement, props);
|
|
2672
2394
|
|
|
2673
2395
|
// Call render() with the same params that would've been passed to the constructor.
|
|
2396
|
+
// We do this even if the arguments haven't changed, so we can let the child component
|
|
2397
|
+
// compare the arguments and then decide for itself whether it wants to re-render.
|
|
2674
2398
|
else if (el.render) {
|
|
2675
|
-
let oldHash = Globals
|
|
2676
|
-
if (oldHash !== newHash) {
|
|
2399
|
+
//let oldHash = Globals.componentArgsHash.get(el);
|
|
2400
|
+
//if (oldHash !== newHash) { // Only if not changed.
|
|
2677
2401
|
let args = {};
|
|
2678
2402
|
for (let name in props || {})
|
|
2679
2403
|
args[Util.dashesToCamel(name)] = props[name];
|
|
2680
2404
|
el.render(args); // Pass new values of props to render so it can decide how it wants to respond.
|
|
2681
|
-
}
|
|
2405
|
+
//}
|
|
2682
2406
|
}
|
|
2683
2407
|
|
|
2684
2408
|
Globals$1.componentArgsHash.set(el, newHash);
|
|
@@ -2691,10 +2415,10 @@ class NodeGroup {
|
|
|
2691
2415
|
* The logic of this function is complex and could use cleaning up.
|
|
2692
2416
|
*
|
|
2693
2417
|
* @param el
|
|
2694
|
-
* @param isPreHtmlElement
|
|
2418
|
+
* @param isPreHtmlElement {?boolean} True if the element's tag name ends with -solarite-placeholder
|
|
2695
2419
|
* @param props {Object} Attributes with dynamic values.
|
|
2696
2420
|
* @return {HTMLElement} */
|
|
2697
|
-
|
|
2421
|
+
instantiateComponent(el, isPreHtmlElement=undefined, props=undefined) {
|
|
2698
2422
|
if (isPreHtmlElement === undefined)
|
|
2699
2423
|
isPreHtmlElement = !el.hasAttribute('_is');
|
|
2700
2424
|
|
|
@@ -2708,24 +2432,24 @@ class NodeGroup {
|
|
|
2708
2432
|
if (!Constructor)
|
|
2709
2433
|
throw new Error(`The custom tag name ${tagName} is not registered.`)
|
|
2710
2434
|
|
|
2711
|
-
let
|
|
2435
|
+
let attribs = {};
|
|
2712
2436
|
for (let name in props || {})
|
|
2713
|
-
|
|
2437
|
+
attribs[Util.dashesToCamel(name)] = props[name];
|
|
2714
2438
|
|
|
2715
2439
|
// Pass other attribs to constructor, since otherwise they're not yet set on the element,
|
|
2716
2440
|
// and the constructor would otherwise have no way to see them.
|
|
2717
2441
|
if (el.attributes.length) {
|
|
2718
2442
|
for (let attrib of el.attributes) {
|
|
2719
2443
|
let attribName = Util.dashesToCamel(attrib.name);
|
|
2720
|
-
if (!
|
|
2721
|
-
|
|
2444
|
+
if (!attribs.hasOwnProperty(attribName) && attribName !== 'solarite-placeholder')
|
|
2445
|
+
attribs[attribName] = attrib.value;
|
|
2722
2446
|
}
|
|
2723
2447
|
}
|
|
2724
2448
|
|
|
2725
2449
|
// Create the web component.
|
|
2726
2450
|
// Get the children that aren't Solarite's comment placeholders.
|
|
2727
|
-
let
|
|
2728
|
-
let newEl = new Constructor(
|
|
2451
|
+
let children = [...el.childNodes].filter(node => node.nodeType !== Node.COMMENT_NODE || !node.nodeValue.startsWith('ExprPath'));
|
|
2452
|
+
let newEl = new Constructor(attribs, children);
|
|
2729
2453
|
|
|
2730
2454
|
if (!isPreHtmlElement)
|
|
2731
2455
|
newEl.setAttribute('is', el.getAttribute('is').toLowerCase());
|
|
@@ -2759,7 +2483,7 @@ class NodeGroup {
|
|
|
2759
2483
|
|
|
2760
2484
|
// Copy attributes over.
|
|
2761
2485
|
for (let attrib of el.attributes)
|
|
2762
|
-
if (attrib.name !== '_is')
|
|
2486
|
+
if (attrib.name !== '_is' && attrib.name !== 'solarite-placeholder')
|
|
2763
2487
|
newEl.setAttribute(attrib.name, attrib.value);
|
|
2764
2488
|
|
|
2765
2489
|
// Set dynamic attributes if they are primitive types.
|
|
@@ -2854,9 +2578,9 @@ class NodeGroup {
|
|
|
2854
2578
|
let result = [];
|
|
2855
2579
|
|
|
2856
2580
|
// static components. These are WebComponents that do not have any constructor arguments that are expressions.
|
|
2857
|
-
// Those are instead created by applyExpr() which calls applyComponentExprs() which calls
|
|
2581
|
+
// Those are instead created by applyExpr() which calls applyComponentExprs() which calls instantiateComponent().
|
|
2858
2582
|
// Maybe someday these two paths will be merged?
|
|
2859
|
-
// Must happen before ids because
|
|
2583
|
+
// Must happen before ids because instantiateComponent will replace the element.
|
|
2860
2584
|
for (let path of shell.staticComponents) {
|
|
2861
2585
|
if (pathOffset)
|
|
2862
2586
|
path = path.slice(0, -pathOffset);
|
|
@@ -2870,13 +2594,13 @@ class NodeGroup {
|
|
|
2870
2594
|
return result;
|
|
2871
2595
|
}
|
|
2872
2596
|
|
|
2873
|
-
|
|
2597
|
+
instantiateStaticComponents(staticComponents) {
|
|
2874
2598
|
for (let el of staticComponents)
|
|
2875
|
-
this.
|
|
2599
|
+
this.instantiateComponent(el);
|
|
2876
2600
|
}
|
|
2877
2601
|
|
|
2878
2602
|
/**
|
|
2879
|
-
* @param root {HTMLElement}
|
|
2603
|
+
* @param root {HTMLElement|DocumentFragment}
|
|
2880
2604
|
* @param shell {Shell}
|
|
2881
2605
|
* @param pathOffset {int} */
|
|
2882
2606
|
activateEmbeds(root, shell, pathOffset=0) {
|
|
@@ -2986,7 +2710,7 @@ class RootNodeGroup extends NodeGroup {
|
|
|
2986
2710
|
|
|
2987
2711
|
// Copy attributes
|
|
2988
2712
|
for (let attrib of fragment.children[0].attributes)
|
|
2989
|
-
if (!el.hasAttribute(attrib.name))
|
|
2713
|
+
if (!el.hasAttribute(attrib.name) && attrib.name !== 'solarite-placeholder')
|
|
2990
2714
|
el.setAttribute(attrib.name, attrib.value);
|
|
2991
2715
|
|
|
2992
2716
|
// Go one level deeper into all of shell's paths.
|
|
@@ -3046,7 +2770,7 @@ class RootNodeGroup extends NodeGroup {
|
|
|
3046
2770
|
// Apply exprs
|
|
3047
2771
|
this.applyExprs(template.exprs);
|
|
3048
2772
|
|
|
3049
|
-
this.
|
|
2773
|
+
this.instantiateStaticComponents(staticComponents);
|
|
3050
2774
|
}
|
|
3051
2775
|
}
|
|
3052
2776
|
}
|
|
@@ -3070,7 +2794,7 @@ function getSingleEl(fragment) {
|
|
|
3070
2794
|
* @returns {boolean} */
|
|
3071
2795
|
function isReplaceEl(fragment, el) {
|
|
3072
2796
|
return fragment.children.length===1
|
|
3073
|
-
&& el.tagName.includes('-')
|
|
2797
|
+
&& el.tagName.includes('-') // TODO: Check for solarite-placeholder attribute instead?
|
|
3074
2798
|
&& fragment.children[0].tagName.replace('-SOLARITE-PLACEHOLDER', '') === el.tagName;
|
|
3075
2799
|
}
|
|
3076
2800
|
|
|
@@ -3233,6 +2957,8 @@ class Template {
|
|
|
3233
2957
|
* @return {Node|HTMLElement|Template} */
|
|
3234
2958
|
function h(htmlStrings=undefined, ...exprs) {
|
|
3235
2959
|
|
|
2960
|
+
if (htmlStrings === undefined && !exprs.length && arguments.length)
|
|
2961
|
+
throw new Error('h() cannot be called with undefined.');
|
|
3236
2962
|
|
|
3237
2963
|
// TODO: Make this a more flat if/else and call other functions for the logic.
|
|
3238
2964
|
if (htmlStrings instanceof Node) {
|
|
@@ -3255,7 +2981,7 @@ function h(htmlStrings=undefined, ...exprs) {
|
|
|
3255
2981
|
}
|
|
3256
2982
|
|
|
3257
2983
|
// 2. Render template created by #4 to element.
|
|
3258
|
-
else
|
|
2984
|
+
else { // instanceof Template
|
|
3259
2985
|
let options = exprs[1];
|
|
3260
2986
|
template.render(parent, options);
|
|
3261
2987
|
|
|
@@ -3266,16 +2992,6 @@ function h(htmlStrings=undefined, ...exprs) {
|
|
|
3266
2992
|
parent.append(this.rootNg.getParentNode());
|
|
3267
2993
|
}
|
|
3268
2994
|
}
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
// null for expr[0], remove whole element.
|
|
3273
|
-
// This path never happens?
|
|
3274
|
-
else {
|
|
3275
|
-
throw new Error('unsupported');
|
|
3276
|
-
//let ngm = NodeGroupManager.get(parent);
|
|
3277
|
-
//ngm.render(null, exprs[1])
|
|
3278
|
-
}
|
|
3279
2995
|
}
|
|
3280
2996
|
|
|
3281
2997
|
// 3. Path if used as a template tag.
|
|
@@ -3412,7 +3128,7 @@ let renderF = 'render';
|
|
|
3412
3128
|
* @param el {HTMLElement}
|
|
3413
3129
|
* @param attributeName {string} Attribute name. Not case-sensitive.
|
|
3414
3130
|
* @param defaultValue {*} Default value to use if attribute doesn't exist.
|
|
3415
|
-
* @param type {ArgType|function|*[]}
|
|
3131
|
+
* @param type {ArgType|function|Class|*[]}
|
|
3416
3132
|
* If an array, use the value if it's in the array, otherwise return undefined.
|
|
3417
3133
|
* If it's a function, pass the value to the function and return the result.
|
|
3418
3134
|
* @param fallback {*} If the defaultValue is undefiend and type can't be parsed as the given type, use this value.
|
|
@@ -3427,8 +3143,11 @@ function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String,
|
|
|
3427
3143
|
if (Array.isArray(type))
|
|
3428
3144
|
return type.includes(val) ? val : fallback;
|
|
3429
3145
|
|
|
3430
|
-
if (typeof type === 'function')
|
|
3431
|
-
return type
|
|
3146
|
+
if (typeof type === 'function') {
|
|
3147
|
+
return type.constructor
|
|
3148
|
+
? new type(val) // arg type is custom Class
|
|
3149
|
+
: type(val); // arg type is custom function
|
|
3150
|
+
}
|
|
3432
3151
|
|
|
3433
3152
|
// If bool, it's true as long as it exists and its value isn't falsey.
|
|
3434
3153
|
if (type===ArgType.Bool) {
|
|
@@ -3470,6 +3189,24 @@ function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String,
|
|
|
3470
3189
|
}
|
|
3471
3190
|
}
|
|
3472
3191
|
|
|
3192
|
+
|
|
3193
|
+
/**
|
|
3194
|
+
* Experimental. Set multiple arguments/attributes all at once.
|
|
3195
|
+
* @param el {HTMLElement}
|
|
3196
|
+
* @param args {Record<string, any>}
|
|
3197
|
+
* @param types {Record<string, ArgType|function|Class>}
|
|
3198
|
+
*
|
|
3199
|
+
* @example
|
|
3200
|
+
* constructor({user, path}={}) {
|
|
3201
|
+
* setArgs(this, arguments[0], {user: User, path: ArgType.String});
|
|
3202
|
+
* }
|
|
3203
|
+
*/
|
|
3204
|
+
function setArgs(el, args, types) {
|
|
3205
|
+
for (let name in args)
|
|
3206
|
+
this[name] = getArg(el, name, args[name], types[name] || ArgType.String);
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
|
|
3473
3210
|
/**
|
|
3474
3211
|
* @enum */
|
|
3475
3212
|
var ArgType = {
|
|
@@ -3498,6 +3235,158 @@ var ArgType = {
|
|
|
3498
3235
|
Eval: 'Eval'
|
|
3499
3236
|
};
|
|
3500
3237
|
|
|
3238
|
+
function defineClass(Class, tagName, extendsTag) {
|
|
3239
|
+
if (!customElements[getName](Class)) { // If not previously defined.
|
|
3240
|
+
tagName = tagName || Util.camelToDashes(Class.name);
|
|
3241
|
+
if (!tagName.includes('-'))
|
|
3242
|
+
tagName += '-element';
|
|
3243
|
+
|
|
3244
|
+
let options = null;
|
|
3245
|
+
if (extendsTag)
|
|
3246
|
+
options = {extends: extendsTag};
|
|
3247
|
+
|
|
3248
|
+
customElements[define](tagName, Class, options);
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
/**
|
|
3253
|
+
* Create a version of the Solarite class that extends from the given tag name.
|
|
3254
|
+
* Reasons to inherit from this instead of HTMLElement. None of these are all that useful.
|
|
3255
|
+
* 1. customElements.define() is called automatically when you create the first instance.
|
|
3256
|
+
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
3257
|
+
* 3. Child elements are added before constructor is called. But they're also passed to the constructor. (deprecated?)
|
|
3258
|
+
* 4. We can use this.html = r`...` to set html. (deprecated)
|
|
3259
|
+
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
3260
|
+
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
3261
|
+
* 6. Can we extend from other element types like TR?
|
|
3262
|
+
* 7. Shows default text if render() function isn't defined.
|
|
3263
|
+
*
|
|
3264
|
+
* Advantages to inheriting from HTMLElement
|
|
3265
|
+
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
3266
|
+
* 2. We can inherit from things like HTMLTableRowElement directly.
|
|
3267
|
+
* 3. There's less magic, since everyone is familiar with defining custom elements.
|
|
3268
|
+
*
|
|
3269
|
+
* @param extendsTag {?string}
|
|
3270
|
+
* @return {Class} */
|
|
3271
|
+
function createSolarite(extendsTag=null) {
|
|
3272
|
+
|
|
3273
|
+
let BaseClass = HTMLElement;
|
|
3274
|
+
if (extendsTag && !extendsTag.includes('-')) {
|
|
3275
|
+
extendsTag = extendsTag.toLowerCase();
|
|
3276
|
+
|
|
3277
|
+
BaseClass = Globals$1.elementClasses[extendsTag];
|
|
3278
|
+
if (!BaseClass) { // TODO: Use Cache
|
|
3279
|
+
BaseClass = document.createElement(extendsTag).constructor;
|
|
3280
|
+
Globals$1.elementClasses[extendsTag] = BaseClass;
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
/**
|
|
3285
|
+
* Intercept the construct call to auto-define the class before the constructor is called.
|
|
3286
|
+
* @type {HTMLElement} */
|
|
3287
|
+
let HTMLElementAutoDefine = new Proxy(BaseClass, {
|
|
3288
|
+
construct(Parent, args, Class) {
|
|
3289
|
+
defineClass(Class, null, extendsTag);
|
|
3290
|
+
|
|
3291
|
+
// This is a good place to manipulate any args before they're sent to the constructor.
|
|
3292
|
+
// Such as loading them from attributes, if I could find a way to do so.
|
|
3293
|
+
|
|
3294
|
+
// This line is equivalent the to super() call.
|
|
3295
|
+
return Reflect.construct(Parent, args, Class);
|
|
3296
|
+
}
|
|
3297
|
+
});
|
|
3298
|
+
|
|
3299
|
+
return class Solarite extends HTMLElementAutoDefine {
|
|
3300
|
+
|
|
3301
|
+
|
|
3302
|
+
/**
|
|
3303
|
+
* TODO: Make these standalone functions.
|
|
3304
|
+
* Callbacks.
|
|
3305
|
+
* Use onConnect.push(() => ...); to add new callbacks. */
|
|
3306
|
+
onConnect;
|
|
3307
|
+
|
|
3308
|
+
onFirstConnect;
|
|
3309
|
+
onDisconnect;
|
|
3310
|
+
|
|
3311
|
+
/**
|
|
3312
|
+
* @param options {RenderOptions} */
|
|
3313
|
+
constructor(options={}) {
|
|
3314
|
+
super();
|
|
3315
|
+
|
|
3316
|
+
// TODO: Is options.render ever used?
|
|
3317
|
+
if (options.render===true)
|
|
3318
|
+
this.render();
|
|
3319
|
+
|
|
3320
|
+
else if (options.render===false)
|
|
3321
|
+
Globals$1.rendered.add(this); // Don't render on connectedCallback()
|
|
3322
|
+
|
|
3323
|
+
// Add slot children before constructor code executes.
|
|
3324
|
+
// This breaks the styleStaticNested test.
|
|
3325
|
+
// PendingChildren is setup in NodeGroup.instantiateComponent()
|
|
3326
|
+
// TODO: Match named slots.
|
|
3327
|
+
//let ch = Globals.pendingChildren.pop();
|
|
3328
|
+
//if (ch) // TODO: how could there be a slot before render is called?
|
|
3329
|
+
// (this.querySelector('slot') || this).append(...ch);
|
|
3330
|
+
|
|
3331
|
+
/** @deprecated
|
|
3332
|
+
Object.defineProperty(this, 'html', {
|
|
3333
|
+
set(html) {
|
|
3334
|
+
Globals.rendered.add(this);
|
|
3335
|
+
if (typeof html === 'string') {
|
|
3336
|
+
console.warn("Assigning to this.html without the r template prefix.")
|
|
3337
|
+
this.innerHTML = html;
|
|
3338
|
+
}
|
|
3339
|
+
else
|
|
3340
|
+
this.modifications = r(this, html, options);
|
|
3341
|
+
}
|
|
3342
|
+
})*/
|
|
3343
|
+
|
|
3344
|
+
/*
|
|
3345
|
+
let pthis = new Proxy(this, {
|
|
3346
|
+
get(obj, prop) {
|
|
3347
|
+
return Reflect.get(obj, prop)
|
|
3348
|
+
}
|
|
3349
|
+
});
|
|
3350
|
+
this.render = this.render.bind(pthis);
|
|
3351
|
+
*/
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
/**
|
|
3355
|
+
* Call render() only if it hasn't already been called. */
|
|
3356
|
+
renderFirstTime() {
|
|
3357
|
+
if (!Globals$1.rendered.has(this) && this.render)
|
|
3358
|
+
this.render();
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
/**
|
|
3362
|
+
* Called automatically by the browser. */
|
|
3363
|
+
connectedCallback() {
|
|
3364
|
+
this.renderFirstTime();
|
|
3365
|
+
if (!Globals$1.connected.has(this)) {
|
|
3366
|
+
Globals$1.connected.add(this);
|
|
3367
|
+
if (this.onFirstConnect)
|
|
3368
|
+
this.onFirstConnect();
|
|
3369
|
+
}
|
|
3370
|
+
if (this.onConnect)
|
|
3371
|
+
this.onConnect();
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
disconnectedCallback() {
|
|
3375
|
+
if (this.onDisconnect)
|
|
3376
|
+
this.onDisconnect();
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
|
|
3380
|
+
static define(tagName=null) {
|
|
3381
|
+
defineClass(this, tagName, extendsTag);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
// Trick to prevent minifier from renaming this method.
|
|
3387
|
+
let define = 'define';
|
|
3388
|
+
let getName = 'getName';
|
|
3389
|
+
|
|
3501
3390
|
/**
|
|
3502
3391
|
* Solarite JavasCript UI library.
|
|
3503
3392
|
* MIT License
|
|
@@ -3507,15 +3396,13 @@ var ArgType = {
|
|
|
3507
3396
|
/**
|
|
3508
3397
|
* TODO: The Proxy and the multiple base classes mess up 'instanceof Solarite'
|
|
3509
3398
|
* @type {Node|Class<HTMLElement>|function(tagName:string):Node|Class<HTMLElement>} */
|
|
3510
|
-
|
|
3399
|
+
const Solarite = new Proxy(createSolarite(), {
|
|
3511
3400
|
apply(self, _, args) {
|
|
3512
3401
|
return createSolarite(...args)
|
|
3513
3402
|
}
|
|
3514
3403
|
});
|
|
3515
|
-
let getInputValue = Util.getInputValue;
|
|
3516
3404
|
|
|
3517
|
-
//Experimental:
|
|
3518
3405
|
//export {default as watch, renderWatched} from './watch.js'; // unfinished
|
|
3519
3406
|
|
|
3520
3407
|
export default h;
|
|
3521
|
-
export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, delve, getArg,
|
|
3408
|
+
export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, delve, getArg, h, h as r, setArgs };
|