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-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) {
|
|
@@ -519,273 +532,25 @@ function nodeToArrayTree(node, callback=null) {
|
|
|
519
532
|
}
|
|
520
533
|
|
|
521
534
|
|
|
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);
|
|
761
|
-
}
|
|
762
|
-
isHashing = false;
|
|
763
|
-
return result;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
/**
|
|
767
|
-
* Slower hashing method that supports.
|
|
768
|
-
* @param obj
|
|
769
|
-
* @returns {string} */
|
|
770
|
-
function getObjectHashCircular(obj) {
|
|
771
|
-
|
|
772
|
-
//console.log('circular')
|
|
773
|
-
// Slower version that handles circular references.
|
|
774
|
-
// Just adding any callback at all, even one that just returns the value, makes JSON.stringify() twice as slow.
|
|
775
|
-
const seen = new Set();
|
|
776
|
-
return JSON.stringify(obj, (key, value) => {
|
|
777
|
-
if (typeof value === 'object' && value !== null) {
|
|
778
|
-
if (seen.has(value))
|
|
779
|
-
return getObjectId(value);
|
|
780
|
-
seen.add(value);
|
|
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,20 @@ class ExprPath {
|
|
|
1371
1105
|
for (let ng of oldNodeGroups)
|
|
1372
1106
|
if (!ng.startNode.parentNode)
|
|
1373
1107
|
ng.removeAndSaveOrphans();
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
// Instantiate components created within ${...} expressions.
|
|
1113
|
+
// Embedded style tags are handled elsewhere, but where?
|
|
1114
|
+
for (let el of newNodes) {
|
|
1115
|
+
if (el instanceof HTMLElement) {
|
|
1116
|
+
if (el.hasAttribute('solarite-placeholder'))
|
|
1117
|
+
this.parentNg.instantiateComponent(el);
|
|
1118
|
+
for (let child of el.querySelectorAll('[solarite-placeholder]'))
|
|
1119
|
+
this.parentNg.instantiateComponent(child);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1374
1122
|
}
|
|
1375
1123
|
|
|
1376
1124
|
|
|
@@ -1547,50 +1295,10 @@ class ExprPath {
|
|
|
1547
1295
|
// Arrays and functions.
|
|
1548
1296
|
// I tried iterating over the result of a generator function to avoid this recursion and simplify the code,
|
|
1549
1297
|
// but that consistently made the js-framework-benchmarks a few percentage points slower.
|
|
1550
|
-
else
|
|
1298
|
+
else
|
|
1551
1299
|
this.exprToTemplates(expr, template => {
|
|
1552
1300
|
this.applyExactNodes(template, newNodes, secondPass);
|
|
1553
1301
|
});
|
|
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
1302
|
}
|
|
1595
1303
|
|
|
1596
1304
|
applyMultipleAttribs(node, expr) {
|
|
@@ -1733,7 +1441,7 @@ class ExprPath {
|
|
|
1733
1441
|
// Copies the attribute to the property when the input event fires.
|
|
1734
1442
|
// value=${[this, 'value]'}
|
|
1735
1443
|
// checked=${[this, 'isAgree']}
|
|
1736
|
-
// This same logic is in NodeGroup.
|
|
1444
|
+
// This same logic is in NodeGroup.instantiateComponent() for components.
|
|
1737
1445
|
if (Util.isPath(expr)) {
|
|
1738
1446
|
let [obj, path] = [expr[0], expr.slice(1)];
|
|
1739
1447
|
|
|
@@ -2338,7 +2046,7 @@ class Shell {
|
|
|
2338
2046
|
/** @type {int[][]} Array of paths. Used by activateEmbeds() to quickly find components. */
|
|
2339
2047
|
staticComponents = [];
|
|
2340
2048
|
|
|
2341
|
-
/** @type {{path:int[], attribs:
|
|
2049
|
+
/** @type {{path:int[], attribs:Record<string, string>}[]} */
|
|
2342
2050
|
//componentAttribs = [];
|
|
2343
2051
|
|
|
2344
2052
|
|
|
@@ -2399,7 +2107,7 @@ class Shell {
|
|
|
2399
2107
|
let parts = attr.value.split(/[\ue000-\uf8ff]/g);
|
|
2400
2108
|
if (parts.length > 1) {
|
|
2401
2109
|
let nonEmptyParts = (parts.length === 2 && !parts[0].length && !parts[1].length) ? null : parts;
|
|
2402
|
-
let type = isEvent(attr.name) ? ExprPathType.Event : ExprPathType.AttribValue;
|
|
2110
|
+
let type = Util.isEvent(attr.name) ? ExprPathType.Event : ExprPathType.AttribValue;
|
|
2403
2111
|
|
|
2404
2112
|
this.paths.push(new ExprPath(null, node, type, attr.name, nonEmptyParts));
|
|
2405
2113
|
placeholdersUsed += parts.length - 1;
|
|
@@ -2527,11 +2235,12 @@ class Shell {
|
|
|
2527
2235
|
function addToken(token, context) {
|
|
2528
2236
|
|
|
2529
2237
|
if (context === HtmlParser.Tag) {
|
|
2530
|
-
// Find Solarite Components tags and append -solarite-placeholder to their tag names
|
|
2238
|
+
// Find Solarite Components tags and append -solarite-placeholder to their tag names
|
|
2239
|
+
// and give them a solarite-placeholder attribute so we can easily find them later.
|
|
2531
2240
|
// This way we can gather their constructor arguments and their children before we call their constructor.
|
|
2532
|
-
// Later, NodeGroup.
|
|
2241
|
+
// Later, NodeGroup.instantiateComponent() will replace them with the real components.
|
|
2533
2242
|
// 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');
|
|
2243
|
+
token = token.replace(/^<\/?[a-z][a-z0-9]*-[a-z0-9-]+/i, match => match + '-solarite-placeholder solarite-placeholder');
|
|
2535
2244
|
}
|
|
2536
2245
|
tokens.push(token);
|
|
2537
2246
|
}
|
|
@@ -2702,7 +2411,7 @@ class NodeGroup {
|
|
|
2702
2411
|
// Apply exprs
|
|
2703
2412
|
this.applyExprs(template.exprs);
|
|
2704
2413
|
|
|
2705
|
-
this.
|
|
2414
|
+
this.instantiateStaticComponents(staticComponents);
|
|
2706
2415
|
}
|
|
2707
2416
|
else if (shell)
|
|
2708
2417
|
this.activateEmbeds(fragment, shell);
|
|
@@ -2796,7 +2505,7 @@ class NodeGroup {
|
|
|
2796
2505
|
// Think of having two adjacent components.
|
|
2797
2506
|
// But the dynamicAttribsAdjacet test already passes.
|
|
2798
2507
|
|
|
2799
|
-
// If a component:
|
|
2508
|
+
// If expr is an attribute in a component:
|
|
2800
2509
|
// 1. Instantiate it if it hasn't already been, sending all expr's to its constructor.
|
|
2801
2510
|
// 2. Otherwise send them to its render function.
|
|
2802
2511
|
// Components with no expressions as attributes are instead activated in activateEmbeds().
|
|
@@ -2833,6 +2542,8 @@ class NodeGroup {
|
|
|
2833
2542
|
// TODO: Only do this if we have ExprPaths within styles?
|
|
2834
2543
|
this.updateStyles();
|
|
2835
2544
|
|
|
2545
|
+
|
|
2546
|
+
|
|
2836
2547
|
// Invalidate the nodes cache because we just changed it.
|
|
2837
2548
|
this.nodesCache = null;
|
|
2838
2549
|
|
|
@@ -2857,23 +2568,25 @@ class NodeGroup {
|
|
|
2857
2568
|
// then we could re-use the hash and logic from NodeManager?
|
|
2858
2569
|
let newHash = getObjectHash(props);
|
|
2859
2570
|
|
|
2860
|
-
let isPreHtmlElement = el.
|
|
2571
|
+
let isPreHtmlElement = el.hasAttribute('solarite-placeholder');
|
|
2861
2572
|
let isPreIsElement = el.hasAttribute('_is');
|
|
2862
2573
|
|
|
2863
2574
|
|
|
2864
2575
|
// Instantiate a placeholder.
|
|
2865
2576
|
if (isPreHtmlElement || isPreIsElement)
|
|
2866
|
-
el = this.
|
|
2577
|
+
el = this.instantiateComponent(el, isPreHtmlElement, props);
|
|
2867
2578
|
|
|
2868
2579
|
// Call render() with the same params that would've been passed to the constructor.
|
|
2580
|
+
// We do this even if the arguments haven't changed, so we can let the child component
|
|
2581
|
+
// compare the arguments and then decide for itself whether it wants to re-render.
|
|
2869
2582
|
else if (el.render) {
|
|
2870
|
-
let oldHash = Globals
|
|
2871
|
-
if (oldHash !== newHash) {
|
|
2583
|
+
//let oldHash = Globals.componentArgsHash.get(el);
|
|
2584
|
+
//if (oldHash !== newHash) { // Only if not changed.
|
|
2872
2585
|
let args = {};
|
|
2873
2586
|
for (let name in props || {})
|
|
2874
2587
|
args[Util.dashesToCamel(name)] = props[name];
|
|
2875
2588
|
el.render(args); // Pass new values of props to render so it can decide how it wants to respond.
|
|
2876
|
-
}
|
|
2589
|
+
//}
|
|
2877
2590
|
}
|
|
2878
2591
|
|
|
2879
2592
|
Globals$1.componentArgsHash.set(el, newHash);
|
|
@@ -2886,10 +2599,10 @@ class NodeGroup {
|
|
|
2886
2599
|
* The logic of this function is complex and could use cleaning up.
|
|
2887
2600
|
*
|
|
2888
2601
|
* @param el
|
|
2889
|
-
* @param isPreHtmlElement
|
|
2602
|
+
* @param isPreHtmlElement {?boolean} True if the element's tag name ends with -solarite-placeholder
|
|
2890
2603
|
* @param props {Object} Attributes with dynamic values.
|
|
2891
2604
|
* @return {HTMLElement} */
|
|
2892
|
-
|
|
2605
|
+
instantiateComponent(el, isPreHtmlElement=undefined, props=undefined) {
|
|
2893
2606
|
if (isPreHtmlElement === undefined)
|
|
2894
2607
|
isPreHtmlElement = !el.hasAttribute('_is');
|
|
2895
2608
|
|
|
@@ -2912,7 +2625,7 @@ class NodeGroup {
|
|
|
2912
2625
|
if (el.attributes.length) {
|
|
2913
2626
|
for (let attrib of el.attributes) {
|
|
2914
2627
|
let attribName = Util.dashesToCamel(attrib.name);
|
|
2915
|
-
if (!args.hasOwnProperty(attribName))
|
|
2628
|
+
if (!args.hasOwnProperty(attribName) && attribName !== 'solarite-placeholder')
|
|
2916
2629
|
args[attribName] = attrib.value;
|
|
2917
2630
|
}
|
|
2918
2631
|
}
|
|
@@ -2954,7 +2667,7 @@ class NodeGroup {
|
|
|
2954
2667
|
|
|
2955
2668
|
// Copy attributes over.
|
|
2956
2669
|
for (let attrib of el.attributes)
|
|
2957
|
-
if (attrib.name !== '_is')
|
|
2670
|
+
if (attrib.name !== '_is' && attrib.name !== 'solarite-placeholder')
|
|
2958
2671
|
newEl.setAttribute(attrib.name, attrib.value);
|
|
2959
2672
|
|
|
2960
2673
|
// Set dynamic attributes if they are primitive types.
|
|
@@ -3114,9 +2827,9 @@ class NodeGroup {
|
|
|
3114
2827
|
let result = [];
|
|
3115
2828
|
|
|
3116
2829
|
// 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
|
|
2830
|
+
// Those are instead created by applyExpr() which calls applyComponentExprs() which calls instantiateComponent().
|
|
3118
2831
|
// Maybe someday these two paths will be merged?
|
|
3119
|
-
// Must happen before ids because
|
|
2832
|
+
// Must happen before ids because instantiateComponent will replace the element.
|
|
3120
2833
|
for (let path of shell.staticComponents) {
|
|
3121
2834
|
if (pathOffset)
|
|
3122
2835
|
path = path.slice(0, -pathOffset);
|
|
@@ -3130,13 +2843,13 @@ class NodeGroup {
|
|
|
3130
2843
|
return result;
|
|
3131
2844
|
}
|
|
3132
2845
|
|
|
3133
|
-
|
|
2846
|
+
instantiateStaticComponents(staticComponents) {
|
|
3134
2847
|
for (let el of staticComponents)
|
|
3135
|
-
this.
|
|
2848
|
+
this.instantiateComponent(el);
|
|
3136
2849
|
}
|
|
3137
2850
|
|
|
3138
2851
|
/**
|
|
3139
|
-
* @param root {HTMLElement}
|
|
2852
|
+
* @param root {HTMLElement|DocumentFragment}
|
|
3140
2853
|
* @param shell {Shell}
|
|
3141
2854
|
* @param pathOffset {int} */
|
|
3142
2855
|
activateEmbeds(root, shell, pathOffset=0) {
|
|
@@ -3246,7 +2959,7 @@ class RootNodeGroup extends NodeGroup {
|
|
|
3246
2959
|
|
|
3247
2960
|
// Copy attributes
|
|
3248
2961
|
for (let attrib of fragment.children[0].attributes)
|
|
3249
|
-
if (!el.hasAttribute(attrib.name))
|
|
2962
|
+
if (!el.hasAttribute(attrib.name) && attrib.name !== 'solarite-placeholder')
|
|
3250
2963
|
el.setAttribute(attrib.name, attrib.value);
|
|
3251
2964
|
|
|
3252
2965
|
// Go one level deeper into all of shell's paths.
|
|
@@ -3306,7 +3019,7 @@ class RootNodeGroup extends NodeGroup {
|
|
|
3306
3019
|
// Apply exprs
|
|
3307
3020
|
this.applyExprs(template.exprs);
|
|
3308
3021
|
|
|
3309
|
-
this.
|
|
3022
|
+
this.instantiateStaticComponents(staticComponents);
|
|
3310
3023
|
}
|
|
3311
3024
|
}
|
|
3312
3025
|
}
|
|
@@ -3330,7 +3043,7 @@ function getSingleEl(fragment) {
|
|
|
3330
3043
|
* @returns {boolean} */
|
|
3331
3044
|
function isReplaceEl(fragment, el) {
|
|
3332
3045
|
return fragment.children.length===1
|
|
3333
|
-
&& el.tagName.includes('-')
|
|
3046
|
+
&& el.tagName.includes('-') // TODO: Check for solarite-placeholder attribute instead?
|
|
3334
3047
|
&& fragment.children[0].tagName.replace('-SOLARITE-PLACEHOLDER', '') === el.tagName;
|
|
3335
3048
|
}
|
|
3336
3049
|
|
|
@@ -3502,6 +3215,8 @@ class Template {
|
|
|
3502
3215
|
* @return {Node|HTMLElement|Template} */
|
|
3503
3216
|
function h(htmlStrings=undefined, ...exprs) {
|
|
3504
3217
|
|
|
3218
|
+
if (htmlStrings === undefined && !exprs.length && arguments.length)
|
|
3219
|
+
throw new Error('h() cannot be called with undefined.');
|
|
3505
3220
|
|
|
3506
3221
|
// TODO: Make this a more flat if/else and call other functions for the logic.
|
|
3507
3222
|
if (htmlStrings instanceof Node) {
|
|
@@ -3524,7 +3239,7 @@ function h(htmlStrings=undefined, ...exprs) {
|
|
|
3524
3239
|
}
|
|
3525
3240
|
|
|
3526
3241
|
// 2. Render template created by #4 to element.
|
|
3527
|
-
else
|
|
3242
|
+
else { // instanceof Template
|
|
3528
3243
|
let options = exprs[1];
|
|
3529
3244
|
template.render(parent, options);
|
|
3530
3245
|
|
|
@@ -3535,16 +3250,6 @@ function h(htmlStrings=undefined, ...exprs) {
|
|
|
3535
3250
|
parent.append(this.rootNg.getParentNode());
|
|
3536
3251
|
}
|
|
3537
3252
|
}
|
|
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
3253
|
}
|
|
3549
3254
|
|
|
3550
3255
|
// 3. Path if used as a template tag.
|
|
@@ -3681,7 +3386,7 @@ let renderF = 'render';
|
|
|
3681
3386
|
* @param el {HTMLElement}
|
|
3682
3387
|
* @param attributeName {string} Attribute name. Not case-sensitive.
|
|
3683
3388
|
* @param defaultValue {*} Default value to use if attribute doesn't exist.
|
|
3684
|
-
* @param type {ArgType|function|*[]}
|
|
3389
|
+
* @param type {ArgType|function|Class|*[]}
|
|
3685
3390
|
* If an array, use the value if it's in the array, otherwise return undefined.
|
|
3686
3391
|
* If it's a function, pass the value to the function and return the result.
|
|
3687
3392
|
* @param fallback {*} If the defaultValue is undefiend and type can't be parsed as the given type, use this value.
|
|
@@ -3696,8 +3401,11 @@ function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String,
|
|
|
3696
3401
|
if (Array.isArray(type))
|
|
3697
3402
|
return type.includes(val) ? val : fallback;
|
|
3698
3403
|
|
|
3699
|
-
if (typeof type === 'function')
|
|
3700
|
-
return type
|
|
3404
|
+
if (typeof type === 'function') {
|
|
3405
|
+
return type.constructor
|
|
3406
|
+
? new type(val) // arg type is custom Class
|
|
3407
|
+
: type(val); // arg type is custom function
|
|
3408
|
+
}
|
|
3701
3409
|
|
|
3702
3410
|
// If bool, it's true as long as it exists and its value isn't falsey.
|
|
3703
3411
|
if (type===ArgType.Bool) {
|
|
@@ -3739,6 +3447,24 @@ function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String,
|
|
|
3739
3447
|
}
|
|
3740
3448
|
}
|
|
3741
3449
|
|
|
3450
|
+
|
|
3451
|
+
/**
|
|
3452
|
+
* Experimental. Set multiple arguments/attributes all at once.
|
|
3453
|
+
* @param el {HTMLElement}
|
|
3454
|
+
* @param args {Record<string, any>}
|
|
3455
|
+
* @param types {Record<string, ArgType|function|Class>}
|
|
3456
|
+
*
|
|
3457
|
+
* @example
|
|
3458
|
+
* constructor({user, path}={}) {
|
|
3459
|
+
* setArgs(this, arguments[0], {user: User, path: ArgType.String});
|
|
3460
|
+
* }
|
|
3461
|
+
*/
|
|
3462
|
+
function setArgs(el, args, types) {
|
|
3463
|
+
for (let name in args)
|
|
3464
|
+
this[name] = getArg(el, name, args[name], types[name] || ArgType.String);
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3467
|
+
|
|
3742
3468
|
/**
|
|
3743
3469
|
* @enum */
|
|
3744
3470
|
var ArgType = {
|
|
@@ -3767,6 +3493,158 @@ var ArgType = {
|
|
|
3767
3493
|
Eval: 'Eval'
|
|
3768
3494
|
};
|
|
3769
3495
|
|
|
3496
|
+
function defineClass(Class, tagName, extendsTag) {
|
|
3497
|
+
if (!customElements[getName](Class)) { // If not previously defined.
|
|
3498
|
+
tagName = tagName || Util.camelToDashes(Class.name);
|
|
3499
|
+
if (!tagName.includes('-'))
|
|
3500
|
+
tagName += '-element';
|
|
3501
|
+
|
|
3502
|
+
let options = null;
|
|
3503
|
+
if (extendsTag)
|
|
3504
|
+
options = {extends: extendsTag};
|
|
3505
|
+
|
|
3506
|
+
customElements[define](tagName, Class, options);
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
/**
|
|
3511
|
+
* Create a version of the Solarite class that extends from the given tag name.
|
|
3512
|
+
* Reasons to inherit from this instead of HTMLElement. None of these are all that useful.
|
|
3513
|
+
* 1. customElements.define() is called automatically when you create the first instance.
|
|
3514
|
+
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
3515
|
+
* 3. Child elements are added before constructor is called. But they're also passed to the constructor. (deprecated?)
|
|
3516
|
+
* 4. We can use this.html = r`...` to set html. (deprecated)
|
|
3517
|
+
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
3518
|
+
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
3519
|
+
* 6. Can we extend from other element types like TR?
|
|
3520
|
+
* 7. Shows default text if render() function isn't defined.
|
|
3521
|
+
*
|
|
3522
|
+
* Advantages to inheriting from HTMLElement
|
|
3523
|
+
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
3524
|
+
* 2. We can inherit from things like HTMLTableRowElement directly.
|
|
3525
|
+
* 3. There's less magic, since everyone is familiar with defining custom elements.
|
|
3526
|
+
*
|
|
3527
|
+
* @param extendsTag {?string}
|
|
3528
|
+
* @return {Class} */
|
|
3529
|
+
function createSolarite(extendsTag=null) {
|
|
3530
|
+
|
|
3531
|
+
let BaseClass = HTMLElement;
|
|
3532
|
+
if (extendsTag && !extendsTag.includes('-')) {
|
|
3533
|
+
extendsTag = extendsTag.toLowerCase();
|
|
3534
|
+
|
|
3535
|
+
BaseClass = Globals$1.elementClasses[extendsTag];
|
|
3536
|
+
if (!BaseClass) { // TODO: Use Cache
|
|
3537
|
+
BaseClass = document.createElement(extendsTag).constructor;
|
|
3538
|
+
Globals$1.elementClasses[extendsTag] = BaseClass;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
/**
|
|
3543
|
+
* Intercept the construct call to auto-define the class before the constructor is called.
|
|
3544
|
+
* @type {HTMLElement} */
|
|
3545
|
+
let HTMLElementAutoDefine = new Proxy(BaseClass, {
|
|
3546
|
+
construct(Parent, args, Class) {
|
|
3547
|
+
defineClass(Class, null, extendsTag);
|
|
3548
|
+
|
|
3549
|
+
// This is a good place to manipulate any args before they're sent to the constructor.
|
|
3550
|
+
// Such as loading them from attributes, if I could find a way to do so.
|
|
3551
|
+
|
|
3552
|
+
// This line is equivalent the to super() call.
|
|
3553
|
+
return Reflect.construct(Parent, args, Class);
|
|
3554
|
+
}
|
|
3555
|
+
});
|
|
3556
|
+
|
|
3557
|
+
return class Solarite extends HTMLElementAutoDefine {
|
|
3558
|
+
|
|
3559
|
+
|
|
3560
|
+
/**
|
|
3561
|
+
* TODO: Make these standalone functions.
|
|
3562
|
+
* Callbacks.
|
|
3563
|
+
* Use onConnect.push(() => ...); to add new callbacks. */
|
|
3564
|
+
onConnect;
|
|
3565
|
+
|
|
3566
|
+
onFirstConnect;
|
|
3567
|
+
onDisconnect;
|
|
3568
|
+
|
|
3569
|
+
/**
|
|
3570
|
+
* @param options {RenderOptions} */
|
|
3571
|
+
constructor(options={}) {
|
|
3572
|
+
super();
|
|
3573
|
+
|
|
3574
|
+
// TODO: Is options.render ever used?
|
|
3575
|
+
if (options.render===true)
|
|
3576
|
+
this.render();
|
|
3577
|
+
|
|
3578
|
+
else if (options.render===false)
|
|
3579
|
+
Globals$1.rendered.add(this); // Don't render on connectedCallback()
|
|
3580
|
+
|
|
3581
|
+
// Add slot children before constructor code executes.
|
|
3582
|
+
// This breaks the styleStaticNested test.
|
|
3583
|
+
// PendingChildren is setup in NodeGroup.instantiateComponent()
|
|
3584
|
+
// TODO: Match named slots.
|
|
3585
|
+
//let ch = Globals.pendingChildren.pop();
|
|
3586
|
+
//if (ch) // TODO: how could there be a slot before render is called?
|
|
3587
|
+
// (this.querySelector('slot') || this).append(...ch);
|
|
3588
|
+
|
|
3589
|
+
/** @deprecated
|
|
3590
|
+
Object.defineProperty(this, 'html', {
|
|
3591
|
+
set(html) {
|
|
3592
|
+
Globals.rendered.add(this);
|
|
3593
|
+
if (typeof html === 'string') {
|
|
3594
|
+
console.warn("Assigning to this.html without the r template prefix.")
|
|
3595
|
+
this.innerHTML = html;
|
|
3596
|
+
}
|
|
3597
|
+
else
|
|
3598
|
+
this.modifications = r(this, html, options);
|
|
3599
|
+
}
|
|
3600
|
+
})*/
|
|
3601
|
+
|
|
3602
|
+
/*
|
|
3603
|
+
let pthis = new Proxy(this, {
|
|
3604
|
+
get(obj, prop) {
|
|
3605
|
+
return Reflect.get(obj, prop)
|
|
3606
|
+
}
|
|
3607
|
+
});
|
|
3608
|
+
this.render = this.render.bind(pthis);
|
|
3609
|
+
*/
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
/**
|
|
3613
|
+
* Call render() only if it hasn't already been called. */
|
|
3614
|
+
renderFirstTime() {
|
|
3615
|
+
if (!Globals$1.rendered.has(this) && this.render)
|
|
3616
|
+
this.render();
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
/**
|
|
3620
|
+
* Called automatically by the browser. */
|
|
3621
|
+
connectedCallback() {
|
|
3622
|
+
this.renderFirstTime();
|
|
3623
|
+
if (!Globals$1.connected.has(this)) {
|
|
3624
|
+
Globals$1.connected.add(this);
|
|
3625
|
+
if (this.onFirstConnect)
|
|
3626
|
+
this.onFirstConnect();
|
|
3627
|
+
}
|
|
3628
|
+
if (this.onConnect)
|
|
3629
|
+
this.onConnect();
|
|
3630
|
+
}
|
|
3631
|
+
|
|
3632
|
+
disconnectedCallback() {
|
|
3633
|
+
if (this.onDisconnect)
|
|
3634
|
+
this.onDisconnect();
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
|
|
3638
|
+
static define(tagName=null) {
|
|
3639
|
+
defineClass(this, tagName, extendsTag);
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
// Trick to prevent minifier from renaming this method.
|
|
3645
|
+
let define = 'define';
|
|
3646
|
+
let getName = 'getName';
|
|
3647
|
+
|
|
3770
3648
|
/**
|
|
3771
3649
|
* Solarite JavasCript UI library.
|
|
3772
3650
|
* MIT License
|
|
@@ -3776,15 +3654,13 @@ var ArgType = {
|
|
|
3776
3654
|
/**
|
|
3777
3655
|
* TODO: The Proxy and the multiple base classes mess up 'instanceof Solarite'
|
|
3778
3656
|
* @type {Node|Class<HTMLElement>|function(tagName:string):Node|Class<HTMLElement>} */
|
|
3779
|
-
|
|
3657
|
+
const Solarite = new Proxy(createSolarite(), {
|
|
3780
3658
|
apply(self, _, args) {
|
|
3781
3659
|
return createSolarite(...args)
|
|
3782
3660
|
}
|
|
3783
3661
|
});
|
|
3784
|
-
let getInputValue = Util.getInputValue;
|
|
3785
3662
|
|
|
3786
|
-
//Experimental:
|
|
3787
3663
|
//export {default as watch, renderWatched} from './watch.js'; // unfinished
|
|
3788
3664
|
|
|
3789
3665
|
export default h;
|
|
3790
|
-
export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, delve, getArg,
|
|
3666
|
+
export { ArgType, Globals$1 as Globals, Solarite, Util as SolariteUtil, Template, delve, getArg, h, h as r, setArgs };
|