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-debug.js
CHANGED
|
@@ -1,98 +1,101 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
//#IFDEV
|
|
2
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
3
|
+
function assert(val) {
|
|
4
|
+
if (!val) {
|
|
5
|
+
debugger;
|
|
6
|
+
throw new Error('Assertion failed: ' + val);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
8
9
|
|
|
10
|
+
//#ENDIF
|
|
11
|
+
|
|
12
|
+
let lastObjectId = 1>>>0; // Is a 32-bit int faster to increment than JavaScript's Number, which is a 64-bit float?
|
|
13
|
+
let objectIds = new WeakMap();
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
|
-
*
|
|
12
|
-
|
|
16
|
+
* @param obj {Object|string|Node}
|
|
17
|
+
* @returns {string} */
|
|
18
|
+
function getObjectId(obj) {
|
|
19
|
+
// if (typeof obj === 'function')
|
|
20
|
+
// return obj.toString(); // This fails to detect when a function's bound variables changes.
|
|
21
|
+
|
|
22
|
+
let result = objectIds.get(obj);
|
|
23
|
+
if (!result) { // convert to string, store in result, then add 1 to lastObjectId.
|
|
24
|
+
result = '~\f' + (lastObjectId++); // We use a unique, 2-byte prefix to ensure it doesn't collide w/ strings not from getObjectId()
|
|
25
|
+
objectIds.set(obj, result);
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
13
29
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
};
|
|
30
|
+
/**
|
|
31
|
+
* Control how JSON.stringify() handles Nodes and Functions.
|
|
32
|
+
* Normally, we'd pass a replacer() function argument to JSON.stringify() to handle Nodes and Functions.
|
|
33
|
+
* But that makes JSON.stringify() take twice as long to run.
|
|
34
|
+
* Adding a toJSON method globally on these object prototypes doesn't incur that performance penalty. */
|
|
35
|
+
let isHashing = true;
|
|
36
|
+
function toJSON() {
|
|
37
|
+
return isHashing ? getObjectId(this) : this
|
|
38
|
+
}
|
|
51
39
|
|
|
52
|
-
// Use properties from Array
|
|
53
|
-
for (let prop of Object.getOwnPropertyNames(Array.prototype))
|
|
54
|
-
if (prop !== 'length' && prop !== 'constructor')
|
|
55
|
-
result[prop] = Array.prototype[prop];
|
|
56
40
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
get() { return result.l },
|
|
60
|
-
set(val) { result.l = val;}
|
|
61
|
-
});
|
|
41
|
+
// Node.prototype.toJSON = toJSON;
|
|
42
|
+
// Function.prototype.toJSON = toJSON;
|
|
62
43
|
|
|
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;
|
|
70
44
|
|
|
71
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Get a string that uniquely maps to the values of the given object.
|
|
47
|
+
* If a value in obj changes, calling getObjectHash(obj) will then return a different hash.
|
|
48
|
+
* This is used by NodeGroupManager to create a hash that represents the current values of a NodeGroup.
|
|
49
|
+
*
|
|
50
|
+
* Relies on the Node and Function prototypes being overridden above.
|
|
51
|
+
*
|
|
52
|
+
* Note that passing an integer may collide with the number we get from hashing an object.
|
|
53
|
+
* But we don't handle that case because we need max performance and Solarite never passes integers to this function.
|
|
54
|
+
*
|
|
55
|
+
* @param obj {*}
|
|
56
|
+
* @returns {string} */
|
|
57
|
+
function getObjectHash(obj) {
|
|
72
58
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
59
|
+
// Sometimes these get unassigned by Chrome and Brave 119, as well as Firefox, seemingly randomly!
|
|
60
|
+
// The same tests sometimes pass, sometimes fail, even after browser and OS restarts.
|
|
61
|
+
// So we check the assignments on every run of getObjectHash()
|
|
62
|
+
if (Node.prototype.toJSON !== toJSON) {
|
|
63
|
+
Node.prototype.toJSON = toJSON;
|
|
64
|
+
if (Function.prototype.toJSON !== toJSON) // Will it only unmap one but not the other?
|
|
65
|
+
Function.prototype.toJSON = toJSON;
|
|
66
|
+
}
|
|
76
67
|
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
let result;
|
|
69
|
+
isHashing = true;
|
|
70
|
+
try {
|
|
71
|
+
result = JSON.stringify(obj);
|
|
72
|
+
}
|
|
73
|
+
catch(e) {
|
|
74
|
+
result = getObjectHashCircular(obj);
|
|
75
|
+
}
|
|
76
|
+
isHashing = false;
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Slower hashing method that supports.
|
|
82
|
+
* @param obj
|
|
83
|
+
* @returns {string} */
|
|
84
|
+
function getObjectHashCircular(obj) {
|
|
85
|
+
|
|
86
|
+
//console.log('circular')
|
|
87
|
+
// Slower version that handles circular references.
|
|
88
|
+
// Just adding any callback at all, even one that just returns the value, makes JSON.stringify() twice as slow.
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
return JSON.stringify(obj, (key, value) => {
|
|
91
|
+
if (typeof value === 'object' && value !== null) {
|
|
92
|
+
if (seen.has(value))
|
|
93
|
+
return getObjectId(value);
|
|
94
|
+
seen.add(value);
|
|
91
95
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
96
|
+
return value;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
96
99
|
|
|
97
100
|
var Globals;
|
|
98
101
|
|
|
@@ -119,15 +122,15 @@ function reset() {
|
|
|
119
122
|
div: document.createElement("div"),
|
|
120
123
|
|
|
121
124
|
/**
|
|
122
|
-
* @type {
|
|
125
|
+
* @type {Record<string, Class<Node>>} A map from built-in tag names to the constructors that create them. */
|
|
123
126
|
elementClasses: {},
|
|
124
127
|
|
|
125
|
-
/** @type {
|
|
128
|
+
/** @type {Record<string, boolean>} Key is tag-name.propName. Value is whether it's an attribute.*/
|
|
126
129
|
htmlProps: {},
|
|
127
130
|
|
|
128
131
|
/**
|
|
129
132
|
* Used by ExprPath.applyEventAttrib()
|
|
130
|
-
* @type {WeakMap<Node,
|
|
133
|
+
* @type {WeakMap<Node, Record<eventName:string, [original:function, bound:function, args:*[]]>>} */
|
|
131
134
|
nodeEvents: new WeakMap(),
|
|
132
135
|
|
|
133
136
|
/**
|
|
@@ -162,7 +165,7 @@ function reset() {
|
|
|
162
165
|
* A map of individual untagged strings to their Templates.
|
|
163
166
|
* This way we don't keep creating new Templates for the same string when re-rendering.
|
|
164
167
|
* This is used by ExprPath.applyExactNodes()
|
|
165
|
-
* @type {
|
|
168
|
+
* @type {Record<string, Template>} */
|
|
166
169
|
//stringTemplates: {},
|
|
167
170
|
|
|
168
171
|
reset,
|
|
@@ -221,6 +224,21 @@ let d = {};
|
|
|
221
224
|
|
|
222
225
|
let Util = {
|
|
223
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Returns true if they're the same.
|
|
229
|
+
* @param a
|
|
230
|
+
* @param b
|
|
231
|
+
* @returns {boolean} */
|
|
232
|
+
arraySame(a, b) {
|
|
233
|
+
let aLength = a.length;
|
|
234
|
+
if (aLength !== b.length)
|
|
235
|
+
return false;
|
|
236
|
+
for (let i=0; i<aLength; i++)
|
|
237
|
+
if (a[i] !== b[i])
|
|
238
|
+
return false;
|
|
239
|
+
return true; // the same.
|
|
240
|
+
},
|
|
241
|
+
|
|
224
242
|
bindId(root, el) {
|
|
225
243
|
let id = el.getAttribute('data-id') || el.getAttribute('id');
|
|
226
244
|
if (id) { // If something hasn't removed the id.
|
|
@@ -261,7 +279,6 @@ let Util = {
|
|
|
261
279
|
}
|
|
262
280
|
},
|
|
263
281
|
|
|
264
|
-
|
|
265
282
|
/**
|
|
266
283
|
* Convert a Proper Case name to a name with dashes.
|
|
267
284
|
* Dashes will be placed between letters and numbers.
|
|
@@ -329,17 +346,17 @@ let Util = {
|
|
|
329
346
|
* for (const item of flatten(complexArray))
|
|
330
347
|
* console.log(item); // Outputs: 1, 2, 3, 4, 5, 6, { a: 'object' }, 7, 8, 9
|
|
331
348
|
*/
|
|
332
|
-
*flatten(value) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
},
|
|
349
|
+
// *flatten(value) {
|
|
350
|
+
// if (Array.isArray(value)) {
|
|
351
|
+
// for (const item of value) {
|
|
352
|
+
// yield* Util.flatten(item); // Recursively flatten arrays
|
|
353
|
+
// }
|
|
354
|
+
// } else if (typeof value === 'function') {
|
|
355
|
+
// const result = value();
|
|
356
|
+
// yield* Util.flatten(result); // Recursively flatten the result of a function
|
|
357
|
+
// } else
|
|
358
|
+
// yield value; // Yield primitive values as is
|
|
359
|
+
// },
|
|
343
360
|
|
|
344
361
|
/**
|
|
345
362
|
* Get the value of an input as the most appropriate JavaScript type.
|
|
@@ -361,6 +378,10 @@ let Util = {
|
|
|
361
378
|
return node.value; // String
|
|
362
379
|
},
|
|
363
380
|
|
|
381
|
+
isEvent(attrName) {
|
|
382
|
+
return attrName.startsWith('on') && attrName in Globals$1.div;
|
|
383
|
+
},
|
|
384
|
+
|
|
364
385
|
/**
|
|
365
386
|
* @param el {HTMLElement}
|
|
366
387
|
* @param prop {string}
|
|
@@ -400,9 +421,10 @@ let Util = {
|
|
|
400
421
|
return val === undefined || val === false || val === null;
|
|
401
422
|
},
|
|
402
423
|
|
|
424
|
+
/*
|
|
403
425
|
isPrimitive(val) {
|
|
404
426
|
return typeof val === 'string' || typeof val === 'number'
|
|
405
|
-
}
|
|
427
|
+
},*/
|
|
406
428
|
|
|
407
429
|
/**
|
|
408
430
|
* If val is a function, evaluate it recursively until the result is not a function.
|
|
@@ -420,6 +442,22 @@ let Util = {
|
|
|
420
442
|
return val;
|
|
421
443
|
},
|
|
422
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Use an array as the value of a map, appending to it when we add.
|
|
447
|
+
* Used by watch.js.
|
|
448
|
+
* @param map {Map|WeakMap|Object}
|
|
449
|
+
* @param key
|
|
450
|
+
* @param value */
|
|
451
|
+
mapArrayAdd(map, key, value) {
|
|
452
|
+
let result = map.get(key);
|
|
453
|
+
if (!result) {
|
|
454
|
+
result = [value];
|
|
455
|
+
map.set(key, result);
|
|
456
|
+
}
|
|
457
|
+
else
|
|
458
|
+
result.push(value);
|
|
459
|
+
},
|
|
460
|
+
|
|
423
461
|
/**
|
|
424
462
|
* Remove nodes from the beginning and end that are not:
|
|
425
463
|
* 1. Elements.
|
|
@@ -448,31 +486,6 @@ let Util = {
|
|
|
448
486
|
|
|
449
487
|
|
|
450
488
|
|
|
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
489
|
// For debugging only
|
|
477
490
|
//#IFDEV
|
|
478
491
|
function setIndent(items, level=1) {
|
|
@@ -508,284 +521,36 @@ function nodeToArrayTree(node, callback=null) {
|
|
|
508
521
|
}
|
|
509
522
|
}
|
|
510
523
|
|
|
511
|
-
//let closingTag = `</${node.nodeName.toLowerCase()}>`;
|
|
512
|
-
|
|
513
|
-
result.push(openingTag, ...childrenArray);
|
|
514
|
-
} else if (node.nodeType === 3) {
|
|
515
|
-
result.push("'"+node.nodeValue+"'");
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
return result;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
function flattenAndIndent(inputArray, indent = "") {
|
|
523
|
-
let result = [];
|
|
524
|
-
|
|
525
|
-
for (let item of inputArray) {
|
|
526
|
-
if (Array.isArray(item)) {
|
|
527
|
-
// Recursively handle nested arrays with increased indentation
|
|
528
|
-
result = result.concat(flattenAndIndent(item, indent + " "));
|
|
529
|
-
} else {
|
|
530
|
-
result.push(indent + item);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
return result;
|
|
535
|
-
}
|
|
536
|
-
//#ENDIF
|
|
537
|
-
|
|
538
|
-
function defineClass(Class, tagName, extendsTag) {
|
|
539
|
-
if (!customElements[getName](Class)) { // If not previously defined.
|
|
540
|
-
tagName = tagName || Util.camelToDashes(Class.name);
|
|
541
|
-
if (!tagName.includes('-'))
|
|
542
|
-
tagName += '-element';
|
|
543
|
-
|
|
544
|
-
let options = null;
|
|
545
|
-
if (extendsTag)
|
|
546
|
-
options = {extends: extendsTag};
|
|
547
|
-
|
|
548
|
-
customElements[define](tagName, Class, options);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Create a version of the Solarite class that extends from the given tag name.
|
|
554
|
-
* Reasons to inherit from this instead of HTMLElement. None of these are all that useful.
|
|
555
|
-
* 1. customElements.define() is called automatically when you create the first instance.
|
|
556
|
-
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
557
|
-
* 3. Child elements are added before constructor is called. But they're also passed to the constructor. (deprecated?)
|
|
558
|
-
* 4. We can use this.html = r`...` to set html. (deprecated)
|
|
559
|
-
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
560
|
-
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
561
|
-
* 6. Can we extend from other element types like TR?
|
|
562
|
-
* 7. Shows default text if render() function isn't defined.
|
|
563
|
-
*
|
|
564
|
-
* Advantages to inheriting from HTMLElement
|
|
565
|
-
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
566
|
-
* 2. We can inherit from things like HTMLTableRowElement directly.
|
|
567
|
-
* 3. There's less magic, since everyone is familiar with defining custom elements.
|
|
568
|
-
*
|
|
569
|
-
* @param extendsTag {?string}
|
|
570
|
-
* @return {Class} */
|
|
571
|
-
function createSolarite(extendsTag=null) {
|
|
572
|
-
|
|
573
|
-
let BaseClass = HTMLElement;
|
|
574
|
-
if (extendsTag && !extendsTag.includes('-')) {
|
|
575
|
-
extendsTag = extendsTag.toLowerCase();
|
|
576
|
-
|
|
577
|
-
BaseClass = Globals$1.elementClasses[extendsTag];
|
|
578
|
-
if (!BaseClass) { // TODO: Use Cache
|
|
579
|
-
BaseClass = document.createElement(extendsTag).constructor;
|
|
580
|
-
Globals$1.elementClasses[extendsTag] = BaseClass;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Intercept the construct call to auto-define the class before the constructor is called.
|
|
586
|
-
* @type {HTMLElement} */
|
|
587
|
-
let HTMLElementAutoDefine = new Proxy(BaseClass, {
|
|
588
|
-
construct(Parent, args, Class) {
|
|
589
|
-
defineClass(Class, null, extendsTag);
|
|
590
|
-
|
|
591
|
-
// This is a good place to manipulate any args before they're sent to the constructor.
|
|
592
|
-
// Such as loading them from attributes, if I could find a way to do so.
|
|
593
|
-
|
|
594
|
-
// This line is equivalent the to super() call.
|
|
595
|
-
return Reflect.construct(Parent, args, Class);
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
return class Solarite extends HTMLElementAutoDefine {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* TODO: Make these standalone functions.
|
|
604
|
-
* Callbacks.
|
|
605
|
-
* Use onConnect.push(() => ...); to add new callbacks. */
|
|
606
|
-
onConnect = Util$1.callback();
|
|
607
|
-
|
|
608
|
-
onFirstConnect = Util$1.callback();
|
|
609
|
-
onDisconnect = Util$1.callback();
|
|
610
|
-
|
|
611
|
-
/**
|
|
612
|
-
* @param options {RenderOptions} */
|
|
613
|
-
constructor(options={}) {
|
|
614
|
-
super();
|
|
615
|
-
|
|
616
|
-
// TODO: Is options.render ever used?
|
|
617
|
-
if (options.render===true)
|
|
618
|
-
this.render();
|
|
619
|
-
|
|
620
|
-
else if (options.render===false)
|
|
621
|
-
Globals$1.rendered.add(this); // Don't render on connectedCallback()
|
|
622
|
-
|
|
623
|
-
// Add slot children before constructor code executes.
|
|
624
|
-
// This breaks the styleStaticNested test.
|
|
625
|
-
// PendingChildren is setup in NodeGroup.createNewComponent()
|
|
626
|
-
// TODO: Match named slots.
|
|
627
|
-
//let ch = Globals.pendingChildren.pop();
|
|
628
|
-
//if (ch) // TODO: how could there be a slot before render is called?
|
|
629
|
-
// (this.querySelector('slot') || this).append(...ch);
|
|
630
|
-
|
|
631
|
-
/** @deprecated
|
|
632
|
-
Object.defineProperty(this, 'html', {
|
|
633
|
-
set(html) {
|
|
634
|
-
Globals.rendered.add(this);
|
|
635
|
-
if (typeof html === 'string') {
|
|
636
|
-
console.warn("Assigning to this.html without the r template prefix.")
|
|
637
|
-
this.innerHTML = html;
|
|
638
|
-
}
|
|
639
|
-
else
|
|
640
|
-
this.modifications = r(this, html, options);
|
|
641
|
-
}
|
|
642
|
-
})*/
|
|
643
|
-
|
|
644
|
-
/*
|
|
645
|
-
let pthis = new Proxy(this, {
|
|
646
|
-
get(obj, prop) {
|
|
647
|
-
return Reflect.get(obj, prop)
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
this.render = this.render.bind(pthis);
|
|
651
|
-
*/
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* Call render() only if it hasn't already been called. */
|
|
656
|
-
renderFirstTime() {
|
|
657
|
-
if (!Globals$1.rendered.has(this) && this.render)
|
|
658
|
-
this.render();
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
/**
|
|
662
|
-
* Called automatically by the browser. */
|
|
663
|
-
connectedCallback() {
|
|
664
|
-
this.renderFirstTime();
|
|
665
|
-
if (!Globals$1.connected.has(this)) {
|
|
666
|
-
Globals$1.connected.add(this);
|
|
667
|
-
this.onFirstConnect();
|
|
668
|
-
}
|
|
669
|
-
this.onConnect();
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
disconnectedCallback() {
|
|
673
|
-
this.onDisconnect();
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
static define(tagName=null) {
|
|
678
|
-
defineClass(this, tagName, extendsTag);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// Trick to prevent minifier from renaming this method.
|
|
684
|
-
let define = 'define';
|
|
685
|
-
let getName = 'getName';
|
|
686
|
-
|
|
687
|
-
//#IFDEV
|
|
688
|
-
/*@__NO_SIDE_EFFECTS__*/
|
|
689
|
-
function assert(val) {
|
|
690
|
-
if (!val) {
|
|
691
|
-
debugger;
|
|
692
|
-
throw new Error('Assertion failed: ' + val);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
//#ENDIF
|
|
697
|
-
|
|
698
|
-
let lastObjectId = 1>>>0; // Is a 32-bit int faster to increment than JavaScript's Number, which is a 64-bit float?
|
|
699
|
-
let objectIds = new WeakMap();
|
|
700
|
-
|
|
701
|
-
/**
|
|
702
|
-
* @param obj {Object|string|Node}
|
|
703
|
-
* @returns {string} */
|
|
704
|
-
function getObjectId(obj) {
|
|
705
|
-
// if (typeof obj === 'function')
|
|
706
|
-
// return obj.toString(); // This fails to detect when a function's bound variables changes.
|
|
707
|
-
|
|
708
|
-
let result = objectIds.get(obj);
|
|
709
|
-
if (!result) { // convert to string, store in result, then add 1 to lastObjectId.
|
|
710
|
-
result = '~\f' + (lastObjectId++); // We use a unique, 2-byte prefix to ensure it doesn't collide w/ strings not from getObjectId()
|
|
711
|
-
objectIds.set(obj, result);
|
|
712
|
-
}
|
|
713
|
-
return result;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* Control how JSON.stringify() handles Nodes and Functions.
|
|
718
|
-
* Normally, we'd pass a replacer() function argument to JSON.stringify() to handle Nodes and Functions.
|
|
719
|
-
* But that makes JSON.stringify() take twice as long to run.
|
|
720
|
-
* Adding a toJSON method globally on these object prototypes doesn't incur that performance penalty. */
|
|
721
|
-
let isHashing = true;
|
|
722
|
-
function toJSON() {
|
|
723
|
-
return isHashing ? getObjectId(this) : this
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
// Node.prototype.toJSON = toJSON;
|
|
728
|
-
// Function.prototype.toJSON = toJSON;
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* Get a string that uniquely maps to the values of the given object.
|
|
733
|
-
* If a value in obj changes, calling getObjectHash(obj) will then return a different hash.
|
|
734
|
-
* This is used by NodeGroupManager to create a hash that represents the current values of a NodeGroup.
|
|
735
|
-
*
|
|
736
|
-
* Relies on the Node and Function prototypes being overridden above.
|
|
737
|
-
*
|
|
738
|
-
* Note that passing an integer may collide with the number we get from hashing an object.
|
|
739
|
-
* But we don't handle that case because we need max performance and Solarite never passes integers to this function.
|
|
740
|
-
*
|
|
741
|
-
* @param obj {*}
|
|
742
|
-
* @returns {string} */
|
|
743
|
-
function getObjectHash(obj) {
|
|
744
|
-
|
|
745
|
-
// Sometimes these get unassigned by Chrome and Brave 119, as well as Firefox, seemingly randomly!
|
|
746
|
-
// The same tests sometimes pass, sometimes fail, even after browser and OS restarts.
|
|
747
|
-
// So we check the assignments on every run of getObjectHash()
|
|
748
|
-
if (Node.prototype.toJSON !== toJSON) {
|
|
749
|
-
Node.prototype.toJSON = toJSON;
|
|
750
|
-
if (Function.prototype.toJSON !== toJSON) // Will it only unmap one but not the other?
|
|
751
|
-
Function.prototype.toJSON = toJSON;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
let result;
|
|
755
|
-
isHashing = true;
|
|
756
|
-
try {
|
|
757
|
-
result = JSON.stringify(obj);
|
|
758
|
-
}
|
|
759
|
-
catch(e) {
|
|
760
|
-
result = getObjectHashCircular(obj);
|
|
524
|
+
//let closingTag = `</${node.nodeName.toLowerCase()}>`;
|
|
525
|
+
|
|
526
|
+
result.push(openingTag, ...childrenArray);
|
|
527
|
+
} else if (node.nodeType === 3) {
|
|
528
|
+
result.push("'"+node.nodeValue+"'");
|
|
761
529
|
}
|
|
762
|
-
|
|
530
|
+
|
|
763
531
|
return result;
|
|
764
532
|
}
|
|
765
533
|
|
|
766
|
-
/**
|
|
767
|
-
* Slower hashing method that supports.
|
|
768
|
-
* @param obj
|
|
769
|
-
* @returns {string} */
|
|
770
|
-
function getObjectHashCircular(obj) {
|
|
771
534
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
535
|
+
function flattenAndIndent(inputArray, indent = "") {
|
|
536
|
+
let result = [];
|
|
537
|
+
|
|
538
|
+
for (let item of inputArray) {
|
|
539
|
+
if (Array.isArray(item)) {
|
|
540
|
+
// Recursively handle nested arrays with increased indentation
|
|
541
|
+
result = result.concat(flattenAndIndent(item, indent + " "));
|
|
542
|
+
} else {
|
|
543
|
+
result.push(indent + item);
|
|
781
544
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
549
|
+
//#ENDIF
|
|
785
550
|
|
|
786
551
|
class MultiValueMap {
|
|
787
552
|
|
|
788
|
-
/** @type {
|
|
553
|
+
/** @type {Record<string, Set>} */
|
|
789
554
|
data = {};
|
|
790
555
|
|
|
791
556
|
// Set a new value for a key
|
|
@@ -879,37 +644,6 @@ class MultiValueMap {
|
|
|
879
644
|
return result;
|
|
880
645
|
}
|
|
881
646
|
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* Try to delete an item that matches the key and the isPreferred function.
|
|
885
|
-
* if not the latter, just delete any item that matches the key.
|
|
886
|
-
* @param key {string}
|
|
887
|
-
* @returns {*|undefined} The deleted item. */
|
|
888
|
-
deletePreferred(key, parent) {
|
|
889
|
-
let result;
|
|
890
|
-
let data = this.data;
|
|
891
|
-
let set = data[key];
|
|
892
|
-
if (!set)
|
|
893
|
-
return undefined;
|
|
894
|
-
|
|
895
|
-
for (let val of set) {
|
|
896
|
-
if (val?.parentNode === parent) {
|
|
897
|
-
set.delete(val);
|
|
898
|
-
result = val;
|
|
899
|
-
break;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
if (!result) {
|
|
903
|
-
[result] = set;
|
|
904
|
-
set.delete(result);
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
if (set.size === 0)
|
|
908
|
-
delete data[key];
|
|
909
|
-
|
|
910
|
-
return result;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
647
|
hasValue(val) {
|
|
914
648
|
let data = this.data;
|
|
915
649
|
let names = [];
|
|
@@ -1349,7 +1083,7 @@ class ExprPath {
|
|
|
1349
1083
|
|
|
1350
1084
|
|
|
1351
1085
|
// This pre-check makes it a few percent faster?
|
|
1352
|
-
let same = arraySame(oldNodes, newNodes);
|
|
1086
|
+
let same = Util.arraySame(oldNodes, newNodes);
|
|
1353
1087
|
if (!same) {
|
|
1354
1088
|
|
|
1355
1089
|
path.nodesCache = newNodes; // Replaces value set by path.getNodes()
|
|
@@ -1371,6 +1105,17 @@ class ExprPath {
|
|
|
1371
1105
|
for (let ng of oldNodeGroups)
|
|
1372
1106
|
if (!ng.startNode.parentNode)
|
|
1373
1107
|
ng.removeAndSaveOrphans();
|
|
1108
|
+
|
|
1109
|
+
// Instantiate components created within ${...} expressions.
|
|
1110
|
+
// Embedded style tags are handled elsewhere, but where?
|
|
1111
|
+
for (let el of newNodes) {
|
|
1112
|
+
if (el instanceof HTMLElement) {
|
|
1113
|
+
if (el.hasAttribute('solarite-placeholder'))
|
|
1114
|
+
this.parentNg.instantiateComponent(el);
|
|
1115
|
+
for (let child of el.querySelectorAll('[solarite-placeholder]'))
|
|
1116
|
+
this.parentNg.instantiateComponent(child);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1374
1119
|
}
|
|
1375
1120
|
|
|
1376
1121
|
|
|
@@ -1547,50 +1292,10 @@ class ExprPath {
|
|
|
1547
1292
|
// Arrays and functions.
|
|
1548
1293
|
// I tried iterating over the result of a generator function to avoid this recursion and simplify the code,
|
|
1549
1294
|
// but that consistently made the js-framework-benchmarks a few percentage points slower.
|
|
1550
|
-
else
|
|
1295
|
+
else
|
|
1551
1296
|
this.exprToTemplates(expr, template => {
|
|
1552
1297
|
this.applyExactNodes(template, newNodes, secondPass);
|
|
1553
1298
|
});
|
|
1554
|
-
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
// Old version
|
|
1558
|
-
/*else if (Array.isArray(expr))
|
|
1559
|
-
for (let subExpr of expr)
|
|
1560
|
-
this.applyExactNodes(subExpr, newNodes, secondPass);
|
|
1561
|
-
|
|
1562
|
-
else if (typeof expr === 'function') {
|
|
1563
|
-
// TODO: One ExprPath can have multiple expr functions.
|
|
1564
|
-
// But if using it as a watch, it should only have one at the top level.
|
|
1565
|
-
// So maybe this is ok.
|
|
1566
|
-
Globals.currentExprPath = this; // Used by watch()
|
|
1567
|
-
|
|
1568
|
-
this.watchFunction = expr; // TODO: Only do this if it's a top level function.
|
|
1569
|
-
let result = expr(); // As expr accesses watched variables, watch() uses Globals.currentExprPath to mark where those watched variables are being used.
|
|
1570
|
-
Globals.currentExprPath = null;
|
|
1571
|
-
|
|
1572
|
-
this.applyExactNodes(result, newNodes, secondPass);
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
// String
|
|
1576
|
-
else {
|
|
1577
|
-
// Convert expression to a string.
|
|
1578
|
-
let stringExpr = expr;
|
|
1579
|
-
if (expr === undefined || expr === false || expr === null) // Util.isFalsy()
|
|
1580
|
-
stringExpr = '';
|
|
1581
|
-
else if (typeof expr !== 'string')
|
|
1582
|
-
stringExpr = expr + '';
|
|
1583
|
-
|
|
1584
|
-
// Get the same Template for the same string each time.
|
|
1585
|
-
let template = Globals.stringTemplates[stringExpr];
|
|
1586
|
-
if (!template) {
|
|
1587
|
-
template = new Template([stringExpr], []);
|
|
1588
|
-
Globals.stringTemplates[stringExpr] = template;
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
// Recurse.
|
|
1592
|
-
this.applyExactNodes(template, newNodes, secondPass);
|
|
1593
|
-
}*/
|
|
1594
1299
|
}
|
|
1595
1300
|
|
|
1596
1301
|
applyMultipleAttribs(node, expr) {
|
|
@@ -1610,16 +1315,30 @@ class ExprPath {
|
|
|
1610
1315
|
Globals$1.currentExprPath = null;
|
|
1611
1316
|
}
|
|
1612
1317
|
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1318
|
+
// Attribute as name: value object.
|
|
1319
|
+
if (typeof expr === 'object') {
|
|
1320
|
+
for (let name in expr) {
|
|
1321
|
+
let value = expr[name];
|
|
1322
|
+
if (value === undefined || value === false || value === null)
|
|
1323
|
+
continue;
|
|
1324
|
+
node.setAttribute(name, value);
|
|
1325
|
+
this.attrNames.add(name);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1617
1328
|
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1329
|
+
// Attributes as string
|
|
1330
|
+
else {
|
|
1331
|
+
let attrs = (expr + '') // Split string into multiple attributes.
|
|
1332
|
+
.split(/([\w-]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s]+))/g)
|
|
1333
|
+
.map(text => text.trim())
|
|
1334
|
+
.filter(text => text.length);
|
|
1335
|
+
|
|
1336
|
+
for (let attr of attrs) {
|
|
1337
|
+
let [name, value] = attr.split(/\s*=\s*/); // split on first equals.
|
|
1338
|
+
value = (value || '').replace(/^(['"])(.*)\1$/, '$2'); // trim value quotes if they match.
|
|
1339
|
+
node.setAttribute(name, value);
|
|
1340
|
+
this.attrNames.add(name);
|
|
1341
|
+
}
|
|
1623
1342
|
}
|
|
1624
1343
|
}
|
|
1625
1344
|
|
|
@@ -1733,7 +1452,7 @@ class ExprPath {
|
|
|
1733
1452
|
// Copies the attribute to the property when the input event fires.
|
|
1734
1453
|
// value=${[this, 'value]'}
|
|
1735
1454
|
// checked=${[this, 'isAgree']}
|
|
1736
|
-
// This same logic is in NodeGroup.
|
|
1455
|
+
// This same logic is in NodeGroup.instantiateComponent() for components.
|
|
1737
1456
|
if (Util.isPath(expr)) {
|
|
1738
1457
|
let [obj, path] = [expr[0], expr.slice(1)];
|
|
1739
1458
|
|
|
@@ -2338,7 +2057,7 @@ class Shell {
|
|
|
2338
2057
|
/** @type {int[][]} Array of paths. Used by activateEmbeds() to quickly find components. */
|
|
2339
2058
|
staticComponents = [];
|
|
2340
2059
|
|
|
2341
|
-
/** @type {{path:int[], attribs:
|
|
2060
|
+
/** @type {{path:int[], attribs:Record<string, string>}[]} */
|
|
2342
2061
|
//componentAttribs = [];
|
|
2343
2062
|
|
|
2344
2063
|
|
|
@@ -2399,7 +2118,7 @@ class Shell {
|
|
|
2399
2118
|
let parts = attr.value.split(/[\ue000-\uf8ff]/g);
|
|
2400
2119
|
if (parts.length > 1) {
|
|
2401
2120
|
let nonEmptyParts = (parts.length === 2 && !parts[0].length && !parts[1].length) ? null : parts;
|
|
2402
|
-
let type = isEvent(attr.name) ? ExprPathType.Event : ExprPathType.AttribValue;
|
|
2121
|
+
let type = Util.isEvent(attr.name) ? ExprPathType.Event : ExprPathType.AttribValue;
|
|
2403
2122
|
|
|
2404
2123
|
this.paths.push(new ExprPath(null, node, type, attr.name, nonEmptyParts));
|
|
2405
2124
|
placeholdersUsed += parts.length - 1;
|
|
@@ -2527,11 +2246,12 @@ class Shell {
|
|
|
2527
2246
|
function addToken(token, context) {
|
|
2528
2247
|
|
|
2529
2248
|
if (context === HtmlParser.Tag) {
|
|
2530
|
-
// Find Solarite Components tags and append -solarite-placeholder to their tag names
|
|
2249
|
+
// Find Solarite Components tags and append -solarite-placeholder to their tag names
|
|
2250
|
+
// and give them a solarite-placeholder attribute so we can easily find them later.
|
|
2531
2251
|
// This way we can gather their constructor arguments and their children before we call their constructor.
|
|
2532
|
-
// Later, NodeGroup.
|
|
2252
|
+
// Later, NodeGroup.instantiateComponent() will replace them with the real components.
|
|
2533
2253
|
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
2534
|
-
token = token.replace(/^<\/?[a-z][a-z0-9]*-[a-z0-9-]+/i, match => match + '-solarite-placeholder');
|
|
2254
|
+
token = token.replace(/^<\/?[a-z][a-z0-9]*-[a-z0-9-]+/i, match => match + '-solarite-placeholder solarite-placeholder');
|
|
2535
2255
|
}
|
|
2536
2256
|
tokens.push(token);
|
|
2537
2257
|
}
|
|
@@ -2702,7 +2422,7 @@ class NodeGroup {
|
|
|
2702
2422
|
// Apply exprs
|
|
2703
2423
|
this.applyExprs(template.exprs);
|
|
2704
2424
|
|
|
2705
|
-
this.
|
|
2425
|
+
this.instantiateStaticComponents(staticComponents);
|
|
2706
2426
|
}
|
|
2707
2427
|
else if (shell)
|
|
2708
2428
|
this.activateEmbeds(fragment, shell);
|
|
@@ -2796,7 +2516,7 @@ class NodeGroup {
|
|
|
2796
2516
|
// Think of having two adjacent components.
|
|
2797
2517
|
// But the dynamicAttribsAdjacet test already passes.
|
|
2798
2518
|
|
|
2799
|
-
// If a component:
|
|
2519
|
+
// If expr is an attribute in a component:
|
|
2800
2520
|
// 1. Instantiate it if it hasn't already been, sending all expr's to its constructor.
|
|
2801
2521
|
// 2. Otherwise send them to its render function.
|
|
2802
2522
|
// Components with no expressions as attributes are instead activated in activateEmbeds().
|
|
@@ -2833,6 +2553,8 @@ class NodeGroup {
|
|
|
2833
2553
|
// TODO: Only do this if we have ExprPaths within styles?
|
|
2834
2554
|
this.updateStyles();
|
|
2835
2555
|
|
|
2556
|
+
|
|
2557
|
+
|
|
2836
2558
|
// Invalidate the nodes cache because we just changed it.
|
|
2837
2559
|
this.nodesCache = null;
|
|
2838
2560
|
|
|
@@ -2857,23 +2579,25 @@ class NodeGroup {
|
|
|
2857
2579
|
// then we could re-use the hash and logic from NodeManager?
|
|
2858
2580
|
let newHash = getObjectHash(props);
|
|
2859
2581
|
|
|
2860
|
-
let isPreHtmlElement = el.
|
|
2582
|
+
let isPreHtmlElement = el.hasAttribute('solarite-placeholder');
|
|
2861
2583
|
let isPreIsElement = el.hasAttribute('_is');
|
|
2862
2584
|
|
|
2863
2585
|
|
|
2864
2586
|
// Instantiate a placeholder.
|
|
2865
2587
|
if (isPreHtmlElement || isPreIsElement)
|
|
2866
|
-
el = this.
|
|
2588
|
+
el = this.instantiateComponent(el, isPreHtmlElement, props);
|
|
2867
2589
|
|
|
2868
2590
|
// Call render() with the same params that would've been passed to the constructor.
|
|
2591
|
+
// We do this even if the arguments haven't changed, so we can let the child component
|
|
2592
|
+
// compare the arguments and then decide for itself whether it wants to re-render.
|
|
2869
2593
|
else if (el.render) {
|
|
2870
|
-
let oldHash = Globals
|
|
2871
|
-
if (oldHash !== newHash) {
|
|
2594
|
+
//let oldHash = Globals.componentArgsHash.get(el);
|
|
2595
|
+
//if (oldHash !== newHash) { // Only if not changed.
|
|
2872
2596
|
let args = {};
|
|
2873
2597
|
for (let name in props || {})
|
|
2874
2598
|
args[Util.dashesToCamel(name)] = props[name];
|
|
2875
2599
|
el.render(args); // Pass new values of props to render so it can decide how it wants to respond.
|
|
2876
|
-
}
|
|
2600
|
+
//}
|
|
2877
2601
|
}
|
|
2878
2602
|
|
|
2879
2603
|
Globals$1.componentArgsHash.set(el, newHash);
|
|
@@ -2886,10 +2610,10 @@ class NodeGroup {
|
|
|
2886
2610
|
* The logic of this function is complex and could use cleaning up.
|
|
2887
2611
|
*
|
|
2888
2612
|
* @param el
|
|
2889
|
-
* @param isPreHtmlElement
|
|
2613
|
+
* @param isPreHtmlElement {?boolean} True if the element's tag name ends with -solarite-placeholder
|
|
2890
2614
|
* @param props {Object} Attributes with dynamic values.
|
|
2891
2615
|
* @return {HTMLElement} */
|
|
2892
|
-
|
|
2616
|
+
instantiateComponent(el, isPreHtmlElement=undefined, props=undefined) {
|
|
2893
2617
|
if (isPreHtmlElement === undefined)
|
|
2894
2618
|
isPreHtmlElement = !el.hasAttribute('_is');
|
|
2895
2619
|
|
|
@@ -2903,24 +2627,24 @@ class NodeGroup {
|
|
|
2903
2627
|
if (!Constructor)
|
|
2904
2628
|
throw new Error(`The custom tag name ${tagName} is not registered.`)
|
|
2905
2629
|
|
|
2906
|
-
let
|
|
2630
|
+
let attribs = {};
|
|
2907
2631
|
for (let name in props || {})
|
|
2908
|
-
|
|
2632
|
+
attribs[Util.dashesToCamel(name)] = props[name];
|
|
2909
2633
|
|
|
2910
2634
|
// Pass other attribs to constructor, since otherwise they're not yet set on the element,
|
|
2911
2635
|
// and the constructor would otherwise have no way to see them.
|
|
2912
2636
|
if (el.attributes.length) {
|
|
2913
2637
|
for (let attrib of el.attributes) {
|
|
2914
2638
|
let attribName = Util.dashesToCamel(attrib.name);
|
|
2915
|
-
if (!
|
|
2916
|
-
|
|
2639
|
+
if (!attribs.hasOwnProperty(attribName) && attribName !== 'solarite-placeholder')
|
|
2640
|
+
attribs[attribName] = attrib.value;
|
|
2917
2641
|
}
|
|
2918
2642
|
}
|
|
2919
2643
|
|
|
2920
2644
|
// Create the web component.
|
|
2921
2645
|
// Get the children that aren't Solarite's comment placeholders.
|
|
2922
|
-
let
|
|
2923
|
-
let newEl = new Constructor(
|
|
2646
|
+
let children = [...el.childNodes].filter(node => node.nodeType !== Node.COMMENT_NODE || !node.nodeValue.startsWith('ExprPath'));
|
|
2647
|
+
let newEl = new Constructor(attribs, children);
|
|
2924
2648
|
|
|
2925
2649
|
if (!isPreHtmlElement)
|
|
2926
2650
|
newEl.setAttribute('is', el.getAttribute('is').toLowerCase());
|
|
@@ -2954,7 +2678,7 @@ class NodeGroup {
|
|
|
2954
2678
|
|
|
2955
2679
|
// Copy attributes over.
|
|
2956
2680
|
for (let attrib of el.attributes)
|
|
2957
|
-
if (attrib.name !== '_is')
|
|
2681
|
+
if (attrib.name !== '_is' && attrib.name !== 'solarite-placeholder')
|
|
2958
2682
|
newEl.setAttribute(attrib.name, attrib.value);
|
|
2959
2683
|
|
|
2960
2684
|
// Set dynamic attributes if they are primitive types.
|
|
@@ -3114,9 +2838,9 @@ class NodeGroup {
|
|
|
3114
2838
|
let result = [];
|
|
3115
2839
|
|
|
3116
2840
|
// static components. These are WebComponents that do not have any constructor arguments that are expressions.
|
|
3117
|
-
// Those are instead created by applyExpr() which calls applyComponentExprs() which calls
|
|
2841
|
+
// Those are instead created by applyExpr() which calls applyComponentExprs() which calls instantiateComponent().
|
|
3118
2842
|
// Maybe someday these two paths will be merged?
|
|
3119
|
-
// Must happen before ids because
|
|
2843
|
+
// Must happen before ids because instantiateComponent will replace the element.
|
|
3120
2844
|
for (let path of shell.staticComponents) {
|
|
3121
2845
|
if (pathOffset)
|
|
3122
2846
|
path = path.slice(0, -pathOffset);
|
|
@@ -3130,13 +2854,13 @@ class NodeGroup {
|
|
|
3130
2854
|
return result;
|
|
3131
2855
|
}
|
|
3132
2856
|
|
|
3133
|
-
|
|
2857
|
+
instantiateStaticComponents(staticComponents) {
|
|
3134
2858
|
for (let el of staticComponents)
|
|
3135
|
-
this.
|
|
2859
|
+
this.instantiateComponent(el);
|
|
3136
2860
|
}
|
|
3137
2861
|
|
|
3138
2862
|
/**
|
|
3139
|
-
* @param root {HTMLElement}
|
|
2863
|
+
* @param root {HTMLElement|DocumentFragment}
|
|
3140
2864
|
* @param shell {Shell}
|
|
3141
2865
|
* @param pathOffset {int} */
|
|
3142
2866
|
activateEmbeds(root, shell, pathOffset=0) {
|
|
@@ -3246,7 +2970,7 @@ class RootNodeGroup extends NodeGroup {
|
|
|
3246
2970
|
|
|
3247
2971
|
// Copy attributes
|
|
3248
2972
|
for (let attrib of fragment.children[0].attributes)
|
|
3249
|
-
if (!el.hasAttribute(attrib.name))
|
|
2973
|
+
if (!el.hasAttribute(attrib.name) && attrib.name !== 'solarite-placeholder')
|
|
3250
2974
|
el.setAttribute(attrib.name, attrib.value);
|
|
3251
2975
|
|
|
3252
2976
|
// Go one level deeper into all of shell's paths.
|
|
@@ -3306,7 +3030,7 @@ class RootNodeGroup extends NodeGroup {
|
|
|
3306
3030
|
// Apply exprs
|
|
3307
3031
|
this.applyExprs(template.exprs);
|
|
3308
3032
|
|
|
3309
|
-
this.
|
|
3033
|
+
this.instantiateStaticComponents(staticComponents);
|
|
3310
3034
|
}
|
|
3311
3035
|
}
|
|
3312
3036
|
}
|
|
@@ -3330,7 +3054,7 @@ function getSingleEl(fragment) {
|
|
|
3330
3054
|
* @returns {boolean} */
|
|
3331
3055
|
function isReplaceEl(fragment, el) {
|
|
3332
3056
|
return fragment.children.length===1
|
|
3333
|
-
&& el.tagName.includes('-')
|
|
3057
|
+
&& el.tagName.includes('-') // TODO: Check for solarite-placeholder attribute instead?
|
|
3334
3058
|
&& fragment.children[0].tagName.replace('-SOLARITE-PLACEHOLDER', '') === el.tagName;
|
|
3335
3059
|
}
|
|
3336
3060
|
|
|
@@ -3502,6 +3226,8 @@ class Template {
|
|
|
3502
3226
|
* @return {Node|HTMLElement|Template} */
|
|
3503
3227
|
function h(htmlStrings=undefined, ...exprs) {
|
|
3504
3228
|
|
|
3229
|
+
if (htmlStrings === undefined && !exprs.length && arguments.length)
|
|
3230
|
+
throw new Error('h() cannot be called with undefined.');
|
|
3505
3231
|
|
|
3506
3232
|
// TODO: Make this a more flat if/else and call other functions for the logic.
|
|
3507
3233
|
if (htmlStrings instanceof Node) {
|
|
@@ -3524,7 +3250,7 @@ function h(htmlStrings=undefined, ...exprs) {
|
|
|
3524
3250
|
}
|
|
3525
3251
|
|
|
3526
3252
|
// 2. Render template created by #4 to element.
|
|
3527
|
-
else
|
|
3253
|
+
else { // instanceof Template
|
|
3528
3254
|
let options = exprs[1];
|
|
3529
3255
|
template.render(parent, options);
|
|
3530
3256
|
|
|
@@ -3535,16 +3261,6 @@ function h(htmlStrings=undefined, ...exprs) {
|
|
|
3535
3261
|
parent.append(this.rootNg.getParentNode());
|
|
3536
3262
|
}
|
|
3537
3263
|
}
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
// null for expr[0], remove whole element.
|
|
3542
|
-
// This path never happens?
|
|
3543
|
-
else {
|
|
3544
|
-
throw new Error('unsupported');
|
|
3545
|
-
//let ngm = NodeGroupManager.get(parent);
|
|
3546
|
-
//ngm.render(null, exprs[1])
|
|
3547
|
-
}
|
|
3548
3264
|
}
|
|
3549
3265
|
|
|
3550
3266
|
// 3. Path if used as a template tag.
|
|
@@ -3681,7 +3397,7 @@ let renderF = 'render';
|
|
|
3681
3397
|
* @param el {HTMLElement}
|
|
3682
3398
|
* @param attributeName {string} Attribute name. Not case-sensitive.
|
|
3683
3399
|
* @param defaultValue {*} Default value to use if attribute doesn't exist.
|
|
3684
|
-
* @param type {ArgType|function|*[]}
|
|
3400
|
+
* @param type {ArgType|function|Class|*[]}
|
|
3685
3401
|
* If an array, use the value if it's in the array, otherwise return undefined.
|
|
3686
3402
|
* If it's a function, pass the value to the function and return the result.
|
|
3687
3403
|
* @param fallback {*} If the defaultValue is undefiend and type can't be parsed as the given type, use this value.
|
|
@@ -3696,8 +3412,11 @@ function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String,
|
|
|
3696
3412
|
if (Array.isArray(type))
|
|
3697
3413
|
return type.includes(val) ? val : fallback;
|
|
3698
3414
|
|
|
3699
|
-
if (typeof type === 'function')
|
|
3700
|
-
return type
|
|
3415
|
+
if (typeof type === 'function') {
|
|
3416
|
+
return type.constructor
|
|
3417
|
+
? new type(val) // arg type is custom Class
|
|
3418
|
+
: type(val); // arg type is custom function
|
|
3419
|
+
}
|
|
3701
3420
|
|
|
3702
3421
|
// If bool, it's true as long as it exists and its value isn't falsey.
|
|
3703
3422
|
if (type===ArgType.Bool) {
|
|
@@ -3739,6 +3458,24 @@ function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String,
|
|
|
3739
3458
|
}
|
|
3740
3459
|
}
|
|
3741
3460
|
|
|
3461
|
+
|
|
3462
|
+
/**
|
|
3463
|
+
* Experimental. Set multiple arguments/attributes all at once.
|
|
3464
|
+
* @param el {HTMLElement}
|
|
3465
|
+
* @param args {Record<string, any>}
|
|
3466
|
+
* @param types {Record<string, ArgType|function|Class>}
|
|
3467
|
+
*
|
|
3468
|
+
* @example
|
|
3469
|
+
* constructor({user, path}={}) {
|
|
3470
|
+
* setArgs(this, arguments[0], {user: User, path: ArgType.String});
|
|
3471
|
+
* }
|
|
3472
|
+
*/
|
|
3473
|
+
function setArgs(el, args, types) {
|
|
3474
|
+
for (let name in args)
|
|
3475
|
+
this[name] = getArg(el, name, args[name], types[name] || ArgType.String);
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
|
|
3742
3479
|
/**
|
|
3743
3480
|
* @enum */
|
|
3744
3481
|
var ArgType = {
|
|
@@ -3767,6 +3504,158 @@ var ArgType = {
|
|
|
3767
3504
|
Eval: 'Eval'
|
|
3768
3505
|
};
|
|
3769
3506
|
|
|
3507
|
+
function defineClass(Class, tagName, extendsTag) {
|
|
3508
|
+
if (!customElements[getName](Class)) { // If not previously defined.
|
|
3509
|
+
tagName = tagName || Util.camelToDashes(Class.name);
|
|
3510
|
+
if (!tagName.includes('-'))
|
|
3511
|
+
tagName += '-element';
|
|
3512
|
+
|
|
3513
|
+
let options = null;
|
|
3514
|
+
if (extendsTag)
|
|
3515
|
+
options = {extends: extendsTag};
|
|
3516
|
+
|
|
3517
|
+
customElements[define](tagName, Class, options);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
/**
|
|
3522
|
+
* Create a version of the Solarite class that extends from the given tag name.
|
|
3523
|
+
* Reasons to inherit from this instead of HTMLElement. None of these are all that useful.
|
|
3524
|
+
* 1. customElements.define() is called automatically when you create the first instance.
|
|
3525
|
+
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
3526
|
+
* 3. Child elements are added before constructor is called. But they're also passed to the constructor. (deprecated?)
|
|
3527
|
+
* 4. We can use this.html = r`...` to set html. (deprecated)
|
|
3528
|
+
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
3529
|
+
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
3530
|
+
* 6. Can we extend from other element types like TR?
|
|
3531
|
+
* 7. Shows default text if render() function isn't defined.
|
|
3532
|
+
*
|
|
3533
|
+
* Advantages to inheriting from HTMLElement
|
|
3534
|
+
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
3535
|
+
* 2. We can inherit from things like HTMLTableRowElement directly.
|
|
3536
|
+
* 3. There's less magic, since everyone is familiar with defining custom elements.
|
|
3537
|
+
*
|
|
3538
|
+
* @param extendsTag {?string}
|
|
3539
|
+
* @return {Class} */
|
|
3540
|
+
function createSolarite(extendsTag=null) {
|
|
3541
|
+
|
|
3542
|
+
let BaseClass = HTMLElement;
|
|
3543
|
+
if (extendsTag && !extendsTag.includes('-')) {
|
|
3544
|
+
extendsTag = extendsTag.toLowerCase();
|
|
3545
|
+
|
|
3546
|
+
BaseClass = Globals$1.elementClasses[extendsTag];
|
|
3547
|
+
if (!BaseClass) { // TODO: Use Cache
|
|
3548
|
+
BaseClass = document.createElement(extendsTag).constructor;
|
|
3549
|
+
Globals$1.elementClasses[extendsTag] = BaseClass;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
/**
|
|
3554
|
+
* Intercept the construct call to auto-define the class before the constructor is called.
|
|
3555
|
+
* @type {HTMLElement} */
|
|
3556
|
+
let HTMLElementAutoDefine = new Proxy(BaseClass, {
|
|
3557
|
+
construct(Parent, args, Class) {
|
|
3558
|
+
defineClass(Class, null, extendsTag);
|
|
3559
|
+
|
|
3560
|
+
// This is a good place to manipulate any args before they're sent to the constructor.
|
|
3561
|
+
// Such as loading them from attributes, if I could find a way to do so.
|
|
3562
|
+
|
|
3563
|
+
// This line is equivalent the to super() call.
|
|
3564
|
+
return Reflect.construct(Parent, args, Class);
|
|
3565
|
+
}
|
|
3566
|
+
});
|
|
3567
|
+
|
|
3568
|
+
return class Solarite extends HTMLElementAutoDefine {
|
|
3569
|
+
|
|
3570
|
+
|
|
3571
|
+
/**
|
|
3572
|
+
* TODO: Make these standalone functions.
|
|
3573
|
+
* Callbacks.
|
|
3574
|
+
* Use onConnect.push(() => ...); to add new callbacks. */
|
|
3575
|
+
onConnect;
|
|
3576
|
+
|
|
3577
|
+
onFirstConnect;
|
|
3578
|
+
onDisconnect;
|
|
3579
|
+
|
|
3580
|
+
/**
|
|
3581
|
+
* @param options {RenderOptions} */
|
|
3582
|
+
constructor(options={}) {
|
|
3583
|
+
super();
|
|
3584
|
+
|
|
3585
|
+
// TODO: Is options.render ever used?
|
|
3586
|
+
if (options.render===true)
|
|
3587
|
+
this.render();
|
|
3588
|
+
|
|
3589
|
+
else if (options.render===false)
|
|
3590
|
+
Globals$1.rendered.add(this); // Don't render on connectedCallback()
|
|
3591
|
+
|
|
3592
|
+
// Add slot children before constructor code executes.
|
|
3593
|
+
// This breaks the styleStaticNested test.
|
|
3594
|
+
// PendingChildren is setup in NodeGroup.instantiateComponent()
|
|
3595
|
+
// TODO: Match named slots.
|
|
3596
|
+
//let ch = Globals.pendingChildren.pop();
|
|
3597
|
+
//if (ch) // TODO: how could there be a slot before render is called?
|
|
3598
|
+
// (this.querySelector('slot') || this).append(...ch);
|
|
3599
|
+
|
|
3600
|
+
/** @deprecated
|
|
3601
|
+
Object.defineProperty(this, 'html', {
|
|
3602
|
+
set(html) {
|
|
3603
|
+
Globals.rendered.add(this);
|
|
3604
|
+
if (typeof html === 'string') {
|
|
3605
|
+
console.warn("Assigning to this.html without the r template prefix.")
|
|
3606
|
+
this.innerHTML = html;
|
|
3607
|
+
}
|
|
3608
|
+
else
|
|
3609
|
+
this.modifications = r(this, html, options);
|
|
3610
|
+
}
|
|
3611
|
+
})*/
|
|
3612
|
+
|
|
3613
|
+
/*
|
|
3614
|
+
let pthis = new Proxy(this, {
|
|
3615
|
+
get(obj, prop) {
|
|
3616
|
+
return Reflect.get(obj, prop)
|
|
3617
|
+
}
|
|
3618
|
+
});
|
|
3619
|
+
this.render = this.render.bind(pthis);
|
|
3620
|
+
*/
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
/**
|
|
3624
|
+
* Call render() only if it hasn't already been called. */
|
|
3625
|
+
renderFirstTime() {
|
|
3626
|
+
if (!Globals$1.rendered.has(this) && this.render)
|
|
3627
|
+
this.render();
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
/**
|
|
3631
|
+
* Called automatically by the browser. */
|
|
3632
|
+
connectedCallback() {
|
|
3633
|
+
this.renderFirstTime();
|
|
3634
|
+
if (!Globals$1.connected.has(this)) {
|
|
3635
|
+
Globals$1.connected.add(this);
|
|
3636
|
+
if (this.onFirstConnect)
|
|
3637
|
+
this.onFirstConnect();
|
|
3638
|
+
}
|
|
3639
|
+
if (this.onConnect)
|
|
3640
|
+
this.onConnect();
|
|
3641
|
+
}
|
|
3642
|
+
|
|
3643
|
+
disconnectedCallback() {
|
|
3644
|
+
if (this.onDisconnect)
|
|
3645
|
+
this.onDisconnect();
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
|
|
3649
|
+
static define(tagName=null) {
|
|
3650
|
+
defineClass(this, tagName, extendsTag);
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
// Trick to prevent minifier from renaming this method.
|
|
3656
|
+
let define = 'define';
|
|
3657
|
+
let getName = 'getName';
|
|
3658
|
+
|
|
3770
3659
|
/**
|
|
3771
3660
|
* Solarite JavasCript UI library.
|
|
3772
3661
|
* MIT License
|
|
@@ -3776,15 +3665,13 @@ var ArgType = {
|
|
|
3776
3665
|
/**
|
|
3777
3666
|
* TODO: The Proxy and the multiple base classes mess up 'instanceof Solarite'
|
|
3778
3667
|
* @type {Node|Class<HTMLElement>|function(tagName:string):Node|Class<HTMLElement>} */
|
|
3779
|
-
|
|
3668
|
+
const Solarite = new Proxy(createSolarite(), {
|
|
3780
3669
|
apply(self, _, args) {
|
|
3781
3670
|
return createSolarite(...args)
|
|
3782
3671
|
}
|
|
3783
3672
|
});
|
|
3784
|
-
let getInputValue = Util.getInputValue;
|
|
3785
3673
|
|
|
3786
|
-
//Experimental:
|
|
3787
3674
|
//export {default as watch, renderWatched} from './watch.js'; // unfinished
|
|
3788
3675
|
|
|
3789
3676
|
export default h;
|
|
3790
|
-
export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, delve, getArg,
|
|
3677
|
+
export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, delve, getArg, h, h as r, setArgs };
|