solarite 0.3.0 → 0.3.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/dist/Solarite-debug.js +380 -504
- package/dist/Solarite.js +363 -487
- package/dist/Solarite.min.js +2 -2
- package/package.json +5 -6
- package/readme.md +1 -1
- package/src/{solarite/ExprPath.js → ExprPath.js} +23 -49
- 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} +25 -21
- 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
|
-
|
|
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;
|
|
34
|
+
|
|
70
35
|
|
|
71
|
-
|
|
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) {
|
|
72
49
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
}
|
|
76
58
|
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
}
|
|
79
70
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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.
|
|
@@ -442,282 +471,18 @@ let Util = {
|
|
|
442
471
|
while (result.length > 0 && shouldTrimNode(result[result.length - 1]))
|
|
443
472
|
result.pop();
|
|
444
473
|
|
|
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;
|
|
474
|
+
return result;
|
|
684
475
|
}
|
|
476
|
+
};
|
|
685
477
|
|
|
686
|
-
let result;
|
|
687
|
-
isHashing = true;
|
|
688
|
-
try {
|
|
689
|
-
result = JSON.stringify(obj);
|
|
690
|
-
}
|
|
691
|
-
catch(e) {
|
|
692
|
-
result = getObjectHashCircular(obj);
|
|
693
|
-
}
|
|
694
|
-
isHashing = false;
|
|
695
|
-
return result;
|
|
696
|
-
}
|
|
697
478
|
|
|
698
|
-
/**
|
|
699
|
-
* Slower hashing method that supports.
|
|
700
|
-
* @param obj
|
|
701
|
-
* @returns {string} */
|
|
702
|
-
function getObjectHashCircular(obj) {
|
|
703
479
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
// Just adding any callback at all, even one that just returns the value, makes JSON.stringify() twice as slow.
|
|
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
|
-
}
|
|
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,20 @@ class ExprPath {
|
|
|
1259
993
|
for (let ng of oldNodeGroups)
|
|
1260
994
|
if (!ng.startNode.parentNode)
|
|
1261
995
|
ng.removeAndSaveOrphans();
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
// Instantiate components created within ${...} expressions.
|
|
1001
|
+
// Embedded style tags are handled elsewhere, but where?
|
|
1002
|
+
for (let el of newNodes) {
|
|
1003
|
+
if (el instanceof HTMLElement) {
|
|
1004
|
+
if (el.hasAttribute('solarite-placeholder'))
|
|
1005
|
+
this.parentNg.instantiateComponent(el);
|
|
1006
|
+
for (let child of el.querySelectorAll('[solarite-placeholder]'))
|
|
1007
|
+
this.parentNg.instantiateComponent(child);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1262
1010
|
}
|
|
1263
1011
|
|
|
1264
1012
|
|
|
@@ -1433,50 +1181,10 @@ class ExprPath {
|
|
|
1433
1181
|
// Arrays and functions.
|
|
1434
1182
|
// I tried iterating over the result of a generator function to avoid this recursion and simplify the code,
|
|
1435
1183
|
// but that consistently made the js-framework-benchmarks a few percentage points slower.
|
|
1436
|
-
else
|
|
1184
|
+
else
|
|
1437
1185
|
this.exprToTemplates(expr, template => {
|
|
1438
1186
|
this.applyExactNodes(template, newNodes, secondPass);
|
|
1439
1187
|
});
|
|
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
1188
|
}
|
|
1481
1189
|
|
|
1482
1190
|
applyMultipleAttribs(node, expr) {
|
|
@@ -1616,7 +1324,7 @@ class ExprPath {
|
|
|
1616
1324
|
// Copies the attribute to the property when the input event fires.
|
|
1617
1325
|
// value=${[this, 'value]'}
|
|
1618
1326
|
// checked=${[this, 'isAgree']}
|
|
1619
|
-
// This same logic is in NodeGroup.
|
|
1327
|
+
// This same logic is in NodeGroup.instantiateComponent() for components.
|
|
1620
1328
|
if (Util.isPath(expr)) {
|
|
1621
1329
|
let [obj, path] = [expr[0], expr.slice(1)];
|
|
1622
1330
|
|
|
@@ -2156,7 +1864,7 @@ class Shell {
|
|
|
2156
1864
|
/** @type {int[][]} Array of paths. Used by activateEmbeds() to quickly find components. */
|
|
2157
1865
|
staticComponents = [];
|
|
2158
1866
|
|
|
2159
|
-
/** @type {{path:int[], attribs:
|
|
1867
|
+
/** @type {{path:int[], attribs:Record<string, string>}[]} */
|
|
2160
1868
|
//componentAttribs = [];
|
|
2161
1869
|
|
|
2162
1870
|
|
|
@@ -2215,7 +1923,7 @@ class Shell {
|
|
|
2215
1923
|
let parts = attr.value.split(/[\ue000-\uf8ff]/g);
|
|
2216
1924
|
if (parts.length > 1) {
|
|
2217
1925
|
let nonEmptyParts = (parts.length === 2 && !parts[0].length && !parts[1].length) ? null : parts;
|
|
2218
|
-
let type = isEvent(attr.name) ? ExprPathType.Event : ExprPathType.AttribValue;
|
|
1926
|
+
let type = Util.isEvent(attr.name) ? ExprPathType.Event : ExprPathType.AttribValue;
|
|
2219
1927
|
|
|
2220
1928
|
this.paths.push(new ExprPath(null, node, type, attr.name, nonEmptyParts));
|
|
2221
1929
|
placeholdersUsed += parts.length - 1;
|
|
@@ -2343,11 +2051,12 @@ class Shell {
|
|
|
2343
2051
|
function addToken(token, context) {
|
|
2344
2052
|
|
|
2345
2053
|
if (context === HtmlParser.Tag) {
|
|
2346
|
-
// Find Solarite Components tags and append -solarite-placeholder to their tag names
|
|
2054
|
+
// Find Solarite Components tags and append -solarite-placeholder to their tag names
|
|
2055
|
+
// and give them a solarite-placeholder attribute so we can easily find them later.
|
|
2347
2056
|
// This way we can gather their constructor arguments and their children before we call their constructor.
|
|
2348
|
-
// Later, NodeGroup.
|
|
2057
|
+
// Later, NodeGroup.instantiateComponent() will replace them with the real components.
|
|
2349
2058
|
// 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');
|
|
2059
|
+
token = token.replace(/^<\/?[a-z][a-z0-9]*-[a-z0-9-]+/i, match => match + '-solarite-placeholder solarite-placeholder');
|
|
2351
2060
|
}
|
|
2352
2061
|
tokens.push(token);
|
|
2353
2062
|
}
|
|
@@ -2510,7 +2219,7 @@ class NodeGroup {
|
|
|
2510
2219
|
// Apply exprs
|
|
2511
2220
|
this.applyExprs(template.exprs);
|
|
2512
2221
|
|
|
2513
|
-
this.
|
|
2222
|
+
this.instantiateStaticComponents(staticComponents);
|
|
2514
2223
|
}
|
|
2515
2224
|
else if (shell)
|
|
2516
2225
|
this.activateEmbeds(fragment, shell);
|
|
@@ -2603,7 +2312,7 @@ class NodeGroup {
|
|
|
2603
2312
|
// Think of having two adjacent components.
|
|
2604
2313
|
// But the dynamicAttribsAdjacet test already passes.
|
|
2605
2314
|
|
|
2606
|
-
// If a component:
|
|
2315
|
+
// If expr is an attribute in a component:
|
|
2607
2316
|
// 1. Instantiate it if it hasn't already been, sending all expr's to its constructor.
|
|
2608
2317
|
// 2. Otherwise send them to its render function.
|
|
2609
2318
|
// Components with no expressions as attributes are instead activated in activateEmbeds().
|
|
@@ -2640,6 +2349,8 @@ class NodeGroup {
|
|
|
2640
2349
|
// TODO: Only do this if we have ExprPaths within styles?
|
|
2641
2350
|
this.updateStyles();
|
|
2642
2351
|
|
|
2352
|
+
|
|
2353
|
+
|
|
2643
2354
|
// Invalidate the nodes cache because we just changed it.
|
|
2644
2355
|
this.nodesCache = null;
|
|
2645
2356
|
|
|
@@ -2662,23 +2373,25 @@ class NodeGroup {
|
|
|
2662
2373
|
// then we could re-use the hash and logic from NodeManager?
|
|
2663
2374
|
let newHash = getObjectHash(props);
|
|
2664
2375
|
|
|
2665
|
-
let isPreHtmlElement = el.
|
|
2376
|
+
let isPreHtmlElement = el.hasAttribute('solarite-placeholder');
|
|
2666
2377
|
let isPreIsElement = el.hasAttribute('_is');
|
|
2667
2378
|
|
|
2668
2379
|
|
|
2669
2380
|
// Instantiate a placeholder.
|
|
2670
2381
|
if (isPreHtmlElement || isPreIsElement)
|
|
2671
|
-
el = this.
|
|
2382
|
+
el = this.instantiateComponent(el, isPreHtmlElement, props);
|
|
2672
2383
|
|
|
2673
2384
|
// Call render() with the same params that would've been passed to the constructor.
|
|
2385
|
+
// We do this even if the arguments haven't changed, so we can let the child component
|
|
2386
|
+
// compare the arguments and then decide for itself whether it wants to re-render.
|
|
2674
2387
|
else if (el.render) {
|
|
2675
|
-
let oldHash = Globals
|
|
2676
|
-
if (oldHash !== newHash) {
|
|
2388
|
+
//let oldHash = Globals.componentArgsHash.get(el);
|
|
2389
|
+
//if (oldHash !== newHash) { // Only if not changed.
|
|
2677
2390
|
let args = {};
|
|
2678
2391
|
for (let name in props || {})
|
|
2679
2392
|
args[Util.dashesToCamel(name)] = props[name];
|
|
2680
2393
|
el.render(args); // Pass new values of props to render so it can decide how it wants to respond.
|
|
2681
|
-
}
|
|
2394
|
+
//}
|
|
2682
2395
|
}
|
|
2683
2396
|
|
|
2684
2397
|
Globals$1.componentArgsHash.set(el, newHash);
|
|
@@ -2691,10 +2404,10 @@ class NodeGroup {
|
|
|
2691
2404
|
* The logic of this function is complex and could use cleaning up.
|
|
2692
2405
|
*
|
|
2693
2406
|
* @param el
|
|
2694
|
-
* @param isPreHtmlElement
|
|
2407
|
+
* @param isPreHtmlElement {?boolean} True if the element's tag name ends with -solarite-placeholder
|
|
2695
2408
|
* @param props {Object} Attributes with dynamic values.
|
|
2696
2409
|
* @return {HTMLElement} */
|
|
2697
|
-
|
|
2410
|
+
instantiateComponent(el, isPreHtmlElement=undefined, props=undefined) {
|
|
2698
2411
|
if (isPreHtmlElement === undefined)
|
|
2699
2412
|
isPreHtmlElement = !el.hasAttribute('_is');
|
|
2700
2413
|
|
|
@@ -2717,7 +2430,7 @@ class NodeGroup {
|
|
|
2717
2430
|
if (el.attributes.length) {
|
|
2718
2431
|
for (let attrib of el.attributes) {
|
|
2719
2432
|
let attribName = Util.dashesToCamel(attrib.name);
|
|
2720
|
-
if (!args.hasOwnProperty(attribName))
|
|
2433
|
+
if (!args.hasOwnProperty(attribName) && attribName !== 'solarite-placeholder')
|
|
2721
2434
|
args[attribName] = attrib.value;
|
|
2722
2435
|
}
|
|
2723
2436
|
}
|
|
@@ -2759,7 +2472,7 @@ class NodeGroup {
|
|
|
2759
2472
|
|
|
2760
2473
|
// Copy attributes over.
|
|
2761
2474
|
for (let attrib of el.attributes)
|
|
2762
|
-
if (attrib.name !== '_is')
|
|
2475
|
+
if (attrib.name !== '_is' && attrib.name !== 'solarite-placeholder')
|
|
2763
2476
|
newEl.setAttribute(attrib.name, attrib.value);
|
|
2764
2477
|
|
|
2765
2478
|
// Set dynamic attributes if they are primitive types.
|
|
@@ -2854,9 +2567,9 @@ class NodeGroup {
|
|
|
2854
2567
|
let result = [];
|
|
2855
2568
|
|
|
2856
2569
|
// 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
|
|
2570
|
+
// Those are instead created by applyExpr() which calls applyComponentExprs() which calls instantiateComponent().
|
|
2858
2571
|
// Maybe someday these two paths will be merged?
|
|
2859
|
-
// Must happen before ids because
|
|
2572
|
+
// Must happen before ids because instantiateComponent will replace the element.
|
|
2860
2573
|
for (let path of shell.staticComponents) {
|
|
2861
2574
|
if (pathOffset)
|
|
2862
2575
|
path = path.slice(0, -pathOffset);
|
|
@@ -2870,13 +2583,13 @@ class NodeGroup {
|
|
|
2870
2583
|
return result;
|
|
2871
2584
|
}
|
|
2872
2585
|
|
|
2873
|
-
|
|
2586
|
+
instantiateStaticComponents(staticComponents) {
|
|
2874
2587
|
for (let el of staticComponents)
|
|
2875
|
-
this.
|
|
2588
|
+
this.instantiateComponent(el);
|
|
2876
2589
|
}
|
|
2877
2590
|
|
|
2878
2591
|
/**
|
|
2879
|
-
* @param root {HTMLElement}
|
|
2592
|
+
* @param root {HTMLElement|DocumentFragment}
|
|
2880
2593
|
* @param shell {Shell}
|
|
2881
2594
|
* @param pathOffset {int} */
|
|
2882
2595
|
activateEmbeds(root, shell, pathOffset=0) {
|
|
@@ -2986,7 +2699,7 @@ class RootNodeGroup extends NodeGroup {
|
|
|
2986
2699
|
|
|
2987
2700
|
// Copy attributes
|
|
2988
2701
|
for (let attrib of fragment.children[0].attributes)
|
|
2989
|
-
if (!el.hasAttribute(attrib.name))
|
|
2702
|
+
if (!el.hasAttribute(attrib.name) && attrib.name !== 'solarite-placeholder')
|
|
2990
2703
|
el.setAttribute(attrib.name, attrib.value);
|
|
2991
2704
|
|
|
2992
2705
|
// Go one level deeper into all of shell's paths.
|
|
@@ -3046,7 +2759,7 @@ class RootNodeGroup extends NodeGroup {
|
|
|
3046
2759
|
// Apply exprs
|
|
3047
2760
|
this.applyExprs(template.exprs);
|
|
3048
2761
|
|
|
3049
|
-
this.
|
|
2762
|
+
this.instantiateStaticComponents(staticComponents);
|
|
3050
2763
|
}
|
|
3051
2764
|
}
|
|
3052
2765
|
}
|
|
@@ -3070,7 +2783,7 @@ function getSingleEl(fragment) {
|
|
|
3070
2783
|
* @returns {boolean} */
|
|
3071
2784
|
function isReplaceEl(fragment, el) {
|
|
3072
2785
|
return fragment.children.length===1
|
|
3073
|
-
&& el.tagName.includes('-')
|
|
2786
|
+
&& el.tagName.includes('-') // TODO: Check for solarite-placeholder attribute instead?
|
|
3074
2787
|
&& fragment.children[0].tagName.replace('-SOLARITE-PLACEHOLDER', '') === el.tagName;
|
|
3075
2788
|
}
|
|
3076
2789
|
|
|
@@ -3233,6 +2946,8 @@ class Template {
|
|
|
3233
2946
|
* @return {Node|HTMLElement|Template} */
|
|
3234
2947
|
function h(htmlStrings=undefined, ...exprs) {
|
|
3235
2948
|
|
|
2949
|
+
if (htmlStrings === undefined && !exprs.length && arguments.length)
|
|
2950
|
+
throw new Error('h() cannot be called with undefined.');
|
|
3236
2951
|
|
|
3237
2952
|
// TODO: Make this a more flat if/else and call other functions for the logic.
|
|
3238
2953
|
if (htmlStrings instanceof Node) {
|
|
@@ -3255,7 +2970,7 @@ function h(htmlStrings=undefined, ...exprs) {
|
|
|
3255
2970
|
}
|
|
3256
2971
|
|
|
3257
2972
|
// 2. Render template created by #4 to element.
|
|
3258
|
-
else
|
|
2973
|
+
else { // instanceof Template
|
|
3259
2974
|
let options = exprs[1];
|
|
3260
2975
|
template.render(parent, options);
|
|
3261
2976
|
|
|
@@ -3266,16 +2981,6 @@ function h(htmlStrings=undefined, ...exprs) {
|
|
|
3266
2981
|
parent.append(this.rootNg.getParentNode());
|
|
3267
2982
|
}
|
|
3268
2983
|
}
|
|
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
2984
|
}
|
|
3280
2985
|
|
|
3281
2986
|
// 3. Path if used as a template tag.
|
|
@@ -3412,7 +3117,7 @@ let renderF = 'render';
|
|
|
3412
3117
|
* @param el {HTMLElement}
|
|
3413
3118
|
* @param attributeName {string} Attribute name. Not case-sensitive.
|
|
3414
3119
|
* @param defaultValue {*} Default value to use if attribute doesn't exist.
|
|
3415
|
-
* @param type {ArgType|function|*[]}
|
|
3120
|
+
* @param type {ArgType|function|Class|*[]}
|
|
3416
3121
|
* If an array, use the value if it's in the array, otherwise return undefined.
|
|
3417
3122
|
* If it's a function, pass the value to the function and return the result.
|
|
3418
3123
|
* @param fallback {*} If the defaultValue is undefiend and type can't be parsed as the given type, use this value.
|
|
@@ -3427,8 +3132,11 @@ function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String,
|
|
|
3427
3132
|
if (Array.isArray(type))
|
|
3428
3133
|
return type.includes(val) ? val : fallback;
|
|
3429
3134
|
|
|
3430
|
-
if (typeof type === 'function')
|
|
3431
|
-
return type
|
|
3135
|
+
if (typeof type === 'function') {
|
|
3136
|
+
return type.constructor
|
|
3137
|
+
? new type(val) // arg type is custom Class
|
|
3138
|
+
: type(val); // arg type is custom function
|
|
3139
|
+
}
|
|
3432
3140
|
|
|
3433
3141
|
// If bool, it's true as long as it exists and its value isn't falsey.
|
|
3434
3142
|
if (type===ArgType.Bool) {
|
|
@@ -3470,6 +3178,24 @@ function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String,
|
|
|
3470
3178
|
}
|
|
3471
3179
|
}
|
|
3472
3180
|
|
|
3181
|
+
|
|
3182
|
+
/**
|
|
3183
|
+
* Experimental. Set multiple arguments/attributes all at once.
|
|
3184
|
+
* @param el {HTMLElement}
|
|
3185
|
+
* @param args {Record<string, any>}
|
|
3186
|
+
* @param types {Record<string, ArgType|function|Class>}
|
|
3187
|
+
*
|
|
3188
|
+
* @example
|
|
3189
|
+
* constructor({user, path}={}) {
|
|
3190
|
+
* setArgs(this, arguments[0], {user: User, path: ArgType.String});
|
|
3191
|
+
* }
|
|
3192
|
+
*/
|
|
3193
|
+
function setArgs(el, args, types) {
|
|
3194
|
+
for (let name in args)
|
|
3195
|
+
this[name] = getArg(el, name, args[name], types[name] || ArgType.String);
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
|
|
3473
3199
|
/**
|
|
3474
3200
|
* @enum */
|
|
3475
3201
|
var ArgType = {
|
|
@@ -3498,6 +3224,158 @@ var ArgType = {
|
|
|
3498
3224
|
Eval: 'Eval'
|
|
3499
3225
|
};
|
|
3500
3226
|
|
|
3227
|
+
function defineClass(Class, tagName, extendsTag) {
|
|
3228
|
+
if (!customElements[getName](Class)) { // If not previously defined.
|
|
3229
|
+
tagName = tagName || Util.camelToDashes(Class.name);
|
|
3230
|
+
if (!tagName.includes('-'))
|
|
3231
|
+
tagName += '-element';
|
|
3232
|
+
|
|
3233
|
+
let options = null;
|
|
3234
|
+
if (extendsTag)
|
|
3235
|
+
options = {extends: extendsTag};
|
|
3236
|
+
|
|
3237
|
+
customElements[define](tagName, Class, options);
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
/**
|
|
3242
|
+
* Create a version of the Solarite class that extends from the given tag name.
|
|
3243
|
+
* Reasons to inherit from this instead of HTMLElement. None of these are all that useful.
|
|
3244
|
+
* 1. customElements.define() is called automatically when you create the first instance.
|
|
3245
|
+
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
3246
|
+
* 3. Child elements are added before constructor is called. But they're also passed to the constructor. (deprecated?)
|
|
3247
|
+
* 4. We can use this.html = r`...` to set html. (deprecated)
|
|
3248
|
+
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
3249
|
+
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
3250
|
+
* 6. Can we extend from other element types like TR?
|
|
3251
|
+
* 7. Shows default text if render() function isn't defined.
|
|
3252
|
+
*
|
|
3253
|
+
* Advantages to inheriting from HTMLElement
|
|
3254
|
+
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
3255
|
+
* 2. We can inherit from things like HTMLTableRowElement directly.
|
|
3256
|
+
* 3. There's less magic, since everyone is familiar with defining custom elements.
|
|
3257
|
+
*
|
|
3258
|
+
* @param extendsTag {?string}
|
|
3259
|
+
* @return {Class} */
|
|
3260
|
+
function createSolarite(extendsTag=null) {
|
|
3261
|
+
|
|
3262
|
+
let BaseClass = HTMLElement;
|
|
3263
|
+
if (extendsTag && !extendsTag.includes('-')) {
|
|
3264
|
+
extendsTag = extendsTag.toLowerCase();
|
|
3265
|
+
|
|
3266
|
+
BaseClass = Globals$1.elementClasses[extendsTag];
|
|
3267
|
+
if (!BaseClass) { // TODO: Use Cache
|
|
3268
|
+
BaseClass = document.createElement(extendsTag).constructor;
|
|
3269
|
+
Globals$1.elementClasses[extendsTag] = BaseClass;
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
/**
|
|
3274
|
+
* Intercept the construct call to auto-define the class before the constructor is called.
|
|
3275
|
+
* @type {HTMLElement} */
|
|
3276
|
+
let HTMLElementAutoDefine = new Proxy(BaseClass, {
|
|
3277
|
+
construct(Parent, args, Class) {
|
|
3278
|
+
defineClass(Class, null, extendsTag);
|
|
3279
|
+
|
|
3280
|
+
// This is a good place to manipulate any args before they're sent to the constructor.
|
|
3281
|
+
// Such as loading them from attributes, if I could find a way to do so.
|
|
3282
|
+
|
|
3283
|
+
// This line is equivalent the to super() call.
|
|
3284
|
+
return Reflect.construct(Parent, args, Class);
|
|
3285
|
+
}
|
|
3286
|
+
});
|
|
3287
|
+
|
|
3288
|
+
return class Solarite extends HTMLElementAutoDefine {
|
|
3289
|
+
|
|
3290
|
+
|
|
3291
|
+
/**
|
|
3292
|
+
* TODO: Make these standalone functions.
|
|
3293
|
+
* Callbacks.
|
|
3294
|
+
* Use onConnect.push(() => ...); to add new callbacks. */
|
|
3295
|
+
onConnect;
|
|
3296
|
+
|
|
3297
|
+
onFirstConnect;
|
|
3298
|
+
onDisconnect;
|
|
3299
|
+
|
|
3300
|
+
/**
|
|
3301
|
+
* @param options {RenderOptions} */
|
|
3302
|
+
constructor(options={}) {
|
|
3303
|
+
super();
|
|
3304
|
+
|
|
3305
|
+
// TODO: Is options.render ever used?
|
|
3306
|
+
if (options.render===true)
|
|
3307
|
+
this.render();
|
|
3308
|
+
|
|
3309
|
+
else if (options.render===false)
|
|
3310
|
+
Globals$1.rendered.add(this); // Don't render on connectedCallback()
|
|
3311
|
+
|
|
3312
|
+
// Add slot children before constructor code executes.
|
|
3313
|
+
// This breaks the styleStaticNested test.
|
|
3314
|
+
// PendingChildren is setup in NodeGroup.instantiateComponent()
|
|
3315
|
+
// TODO: Match named slots.
|
|
3316
|
+
//let ch = Globals.pendingChildren.pop();
|
|
3317
|
+
//if (ch) // TODO: how could there be a slot before render is called?
|
|
3318
|
+
// (this.querySelector('slot') || this).append(...ch);
|
|
3319
|
+
|
|
3320
|
+
/** @deprecated
|
|
3321
|
+
Object.defineProperty(this, 'html', {
|
|
3322
|
+
set(html) {
|
|
3323
|
+
Globals.rendered.add(this);
|
|
3324
|
+
if (typeof html === 'string') {
|
|
3325
|
+
console.warn("Assigning to this.html without the r template prefix.")
|
|
3326
|
+
this.innerHTML = html;
|
|
3327
|
+
}
|
|
3328
|
+
else
|
|
3329
|
+
this.modifications = r(this, html, options);
|
|
3330
|
+
}
|
|
3331
|
+
})*/
|
|
3332
|
+
|
|
3333
|
+
/*
|
|
3334
|
+
let pthis = new Proxy(this, {
|
|
3335
|
+
get(obj, prop) {
|
|
3336
|
+
return Reflect.get(obj, prop)
|
|
3337
|
+
}
|
|
3338
|
+
});
|
|
3339
|
+
this.render = this.render.bind(pthis);
|
|
3340
|
+
*/
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
/**
|
|
3344
|
+
* Call render() only if it hasn't already been called. */
|
|
3345
|
+
renderFirstTime() {
|
|
3346
|
+
if (!Globals$1.rendered.has(this) && this.render)
|
|
3347
|
+
this.render();
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
/**
|
|
3351
|
+
* Called automatically by the browser. */
|
|
3352
|
+
connectedCallback() {
|
|
3353
|
+
this.renderFirstTime();
|
|
3354
|
+
if (!Globals$1.connected.has(this)) {
|
|
3355
|
+
Globals$1.connected.add(this);
|
|
3356
|
+
if (this.onFirstConnect)
|
|
3357
|
+
this.onFirstConnect();
|
|
3358
|
+
}
|
|
3359
|
+
if (this.onConnect)
|
|
3360
|
+
this.onConnect();
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
disconnectedCallback() {
|
|
3364
|
+
if (this.onDisconnect)
|
|
3365
|
+
this.onDisconnect();
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
|
|
3369
|
+
static define(tagName=null) {
|
|
3370
|
+
defineClass(this, tagName, extendsTag);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
// Trick to prevent minifier from renaming this method.
|
|
3376
|
+
let define = 'define';
|
|
3377
|
+
let getName = 'getName';
|
|
3378
|
+
|
|
3501
3379
|
/**
|
|
3502
3380
|
* Solarite JavasCript UI library.
|
|
3503
3381
|
* MIT License
|
|
@@ -3507,15 +3385,13 @@ var ArgType = {
|
|
|
3507
3385
|
/**
|
|
3508
3386
|
* TODO: The Proxy and the multiple base classes mess up 'instanceof Solarite'
|
|
3509
3387
|
* @type {Node|Class<HTMLElement>|function(tagName:string):Node|Class<HTMLElement>} */
|
|
3510
|
-
|
|
3388
|
+
const Solarite = new Proxy(createSolarite(), {
|
|
3511
3389
|
apply(self, _, args) {
|
|
3512
3390
|
return createSolarite(...args)
|
|
3513
3391
|
}
|
|
3514
3392
|
});
|
|
3515
|
-
let getInputValue = Util.getInputValue;
|
|
3516
3393
|
|
|
3517
|
-
//Experimental:
|
|
3518
3394
|
//export {default as watch, renderWatched} from './watch.js'; // unfinished
|
|
3519
3395
|
|
|
3520
3396
|
export default h;
|
|
3521
|
-
export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, delve, getArg,
|
|
3397
|
+
export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, delve, getArg, h, h as r, setArgs };
|