solarite 0.1.0 → 0.1.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/build/build.bat +1 -0
- package/deno.lock +176 -0
- package/dist/Solarite-debug.js +456 -1118
- package/dist/Solarite.js +438 -1216
- package/dist/Solarite.min.js +2 -2
- package/docs/index.md +366 -216
- package/docs/js/codemirror/themeSolarIce.js +1 -1
- package/docs/js/documentation.js +1 -1
- package/docs/js/ui/FlexResizer.js +1 -2
- package/docs/media/documentation.css +2 -2
- package/index.html +40 -23
- package/package.json +1 -1
- package/readme.md +11 -1
- package/src/solarite/ExprPath.js +92 -3
- package/src/solarite/Globals.js +11 -0
- package/src/solarite/MultiValueMap.js +5 -1
- package/src/solarite/NodeGroup.js +54 -124
- package/src/solarite/NodeGroupManager.js +15 -125
- package/src/solarite/Shell.js +5 -5
- package/src/solarite/Solarite.js +5 -2
- package/src/solarite/Template.js +131 -8
- package/src/solarite/createSolarite.js +18 -6
- package/src/solarite/getArg.js +11 -11
- package/src/solarite/hash.js +1 -1
- package/src/solarite/r.js +24 -14
- package/src/solarite/watch.js +1 -1
- package/src/solarite/watch2.js +3 -2
- package/src/solarite/watch3.js +91 -0
- package/src/unused/onConnect.js +79 -0
- package/tests/Solarite.test.js +281 -34
- package/tests/Testimony.js +274 -67
- package/tests/index.html +3 -34
- package/tests/run.bat +2 -0
- package/tests/NodeGroup.test.js +0 -115
- package/tests/Shell.test.js +0 -75
package/dist/Solarite-debug.js
CHANGED
|
@@ -81,16 +81,6 @@ var Util$1 = {
|
|
|
81
81
|
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
//#IFDEV
|
|
85
|
-
/*@__NO_SIDE_EFFECTS__*/
|
|
86
|
-
function assert(val) {
|
|
87
|
-
if (!val) {
|
|
88
|
-
debugger;
|
|
89
|
-
throw new Error('Assertion failed: ' + val);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
//#ENDIF
|
|
93
|
-
|
|
94
84
|
/**
|
|
95
85
|
* Follow a path into an object.
|
|
96
86
|
* @param obj {object}
|
|
@@ -188,19 +178,19 @@ function getArg(el, name, val=null, type=ArgType.String) {
|
|
|
188
178
|
case ArgType.Float:
|
|
189
179
|
return parseFloat(val);
|
|
190
180
|
case ArgType.String:
|
|
191
|
-
return val
|
|
181
|
+
return [undefined, null, false].includes(val) ? '' : val+'';
|
|
192
182
|
case ArgType.JSON:
|
|
193
|
-
try {
|
|
194
|
-
return JSON.parse(val);
|
|
195
|
-
} catch (e) {
|
|
196
|
-
return val;
|
|
197
|
-
}
|
|
198
183
|
case ArgType.Eval:
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
184
|
+
if (typeof val === 'string' && val.length)
|
|
185
|
+
try {
|
|
186
|
+
if (type === ArgType.JSON)
|
|
187
|
+
return JSON.parse(val);
|
|
188
|
+
else
|
|
189
|
+
return eval(`(${val})`);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
return val;
|
|
192
|
+
}
|
|
193
|
+
else return val;
|
|
204
194
|
default:
|
|
205
195
|
return val;
|
|
206
196
|
}
|
|
@@ -251,7 +241,7 @@ function getObjectId(obj, prefix=null) {
|
|
|
251
241
|
prefix = 'Func';
|
|
252
242
|
else if (typeof obj === 'object')
|
|
253
243
|
prefix = 'Obj';
|
|
254
|
-
|
|
244
|
+
}
|
|
255
245
|
//#ENDIF
|
|
256
246
|
|
|
257
247
|
prefix = prefix || '~\f';
|
|
@@ -354,7 +344,11 @@ class MultiValueMap {
|
|
|
354
344
|
return this.data[key] || [];
|
|
355
345
|
}
|
|
356
346
|
|
|
357
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Remove one value from a key, and return it.
|
|
349
|
+
* @param key {string}
|
|
350
|
+
* @param val If specified, make sure we delete this specific value, if a key exists more than once.
|
|
351
|
+
* @returns {*} */
|
|
358
352
|
delete(key, val=undefined) {
|
|
359
353
|
// if (key === '["Html2",[[["Html3",["F1","A"]],["Html3",["F1","B"]]]]]')
|
|
360
354
|
// debugger;
|
|
@@ -399,6 +393,16 @@ class MultiValueMap {
|
|
|
399
393
|
}
|
|
400
394
|
}
|
|
401
395
|
|
|
396
|
+
//#IFDEV
|
|
397
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
398
|
+
function assert(val) {
|
|
399
|
+
if (!val) {
|
|
400
|
+
debugger;
|
|
401
|
+
throw new Error('Assertion failed: ' + val);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
//#ENDIF
|
|
405
|
+
|
|
402
406
|
let Util = {
|
|
403
407
|
|
|
404
408
|
bindStyles(style, root) {
|
|
@@ -619,6 +623,209 @@ function flattenAndIndent(inputArray, indent = "") {
|
|
|
619
623
|
}
|
|
620
624
|
//#ENDIF
|
|
621
625
|
|
|
626
|
+
/**
|
|
627
|
+
* The html strings and evaluated expressions from an html tagged template.
|
|
628
|
+
* A unique Template is created for each item in a loop.
|
|
629
|
+
* Although the reference to the html strings is shared among templates. */
|
|
630
|
+
class Template {
|
|
631
|
+
|
|
632
|
+
/** @type {(Template|string|function)|(Template|string|function)[]} Evaulated expressions. */
|
|
633
|
+
exprs = []
|
|
634
|
+
|
|
635
|
+
/** @type {string[]} */
|
|
636
|
+
html = [];
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* If true, use this template to replace an existing element, instead of appending children to it.
|
|
640
|
+
* @type {?boolean} */
|
|
641
|
+
replaceMode;
|
|
642
|
+
|
|
643
|
+
/** Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
|
|
644
|
+
hashedFields;
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* @deprecated
|
|
648
|
+
* @type {ExprPath} Used with forEach() from watch.js
|
|
649
|
+
* Set in ExprPath.apply() */
|
|
650
|
+
parentPath;
|
|
651
|
+
|
|
652
|
+
/** @type {NodeGroup} */
|
|
653
|
+
nodeGroup;
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* @type {string[][]} */
|
|
657
|
+
paths = [];
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
*
|
|
661
|
+
* @param htmlStrings {string[]}
|
|
662
|
+
* @param exprs {*[]} */
|
|
663
|
+
constructor(htmlStrings, exprs) {
|
|
664
|
+
this.html = htmlStrings;
|
|
665
|
+
this.exprs = exprs;
|
|
666
|
+
|
|
667
|
+
//this.trace = new Error().stack.split(/\n/g)
|
|
668
|
+
|
|
669
|
+
// Multiple templates can share the same htmlStrings array.
|
|
670
|
+
//this.hashedFields = [getObjectId(htmlStrings), exprs]
|
|
671
|
+
|
|
672
|
+
//#IFDEV
|
|
673
|
+
assert(Array.isArray(htmlStrings));
|
|
674
|
+
assert(Array.isArray(exprs));
|
|
675
|
+
|
|
676
|
+
Object.defineProperty(this, 'debug', {
|
|
677
|
+
get() {
|
|
678
|
+
return JSON.stringify([this.html, this.exprs]);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
//#ENDIF
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Called by JSON.serialize when it encounters a Template.
|
|
686
|
+
* This prevents the hashed version from being too large. */
|
|
687
|
+
toJSON() {
|
|
688
|
+
if (!this.hashedFields)
|
|
689
|
+
this.hashedFields = [getObjectId(this.html, 'Html'), this.exprs];
|
|
690
|
+
|
|
691
|
+
return this.hashedFields
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Render the main template, which may indirectly call renderTemplate() to create children.
|
|
696
|
+
* @param el {HTMLElement}
|
|
697
|
+
* @param options {RenderOptions}
|
|
698
|
+
* @return {?DocumentFragment|HTMLElement} */
|
|
699
|
+
render(el=null, options={}) {
|
|
700
|
+
|
|
701
|
+
let ng;
|
|
702
|
+
if (!el) {
|
|
703
|
+
ng = new NodeGroup(this);
|
|
704
|
+
el = ng.getParentNode();
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
let ngm = NodeGroupManager.get(el);
|
|
708
|
+
if (ng)
|
|
709
|
+
ng.manager = ngm;
|
|
710
|
+
|
|
711
|
+
//#IFDEV
|
|
712
|
+
ngm.modifications = {
|
|
713
|
+
created: [],
|
|
714
|
+
updated: [],
|
|
715
|
+
moved: [],
|
|
716
|
+
deleted: []
|
|
717
|
+
};
|
|
718
|
+
//#ENDIF
|
|
719
|
+
|
|
720
|
+
ngm.options = options;
|
|
721
|
+
ngm.clearSubscribers = false; // Used for deprecated watch() path?
|
|
722
|
+
ngm.mutationWatcherEnabled = false;
|
|
723
|
+
|
|
724
|
+
// Fast path for empty component.
|
|
725
|
+
if (this.html?.length === 1 && !this.html[0]) {
|
|
726
|
+
el.innerHTML = '';
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
|
|
730
|
+
// Find or create a NodeGroup for the template.
|
|
731
|
+
// This updates all nodes from the template.
|
|
732
|
+
let close;
|
|
733
|
+
let exact = ngm.getNodeGroup(this, true);
|
|
734
|
+
if (!exact) {
|
|
735
|
+
close = ngm.getNodeGroup(this, false);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
let firstTime = !ngm.rootNg;
|
|
739
|
+
ngm.rootNg = exact || close;
|
|
740
|
+
|
|
741
|
+
// Reparent NodeGroup
|
|
742
|
+
// TODO: Move this to NodeGroup?
|
|
743
|
+
let parent = ngm.rootNg.getParentNode();
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
// If this is the first time rendering this element.
|
|
747
|
+
if (firstTime) {
|
|
748
|
+
|
|
749
|
+
// Save slot children
|
|
750
|
+
let fragment;
|
|
751
|
+
if (el.childNodes.length) {
|
|
752
|
+
fragment = document.createDocumentFragment();
|
|
753
|
+
fragment.append(...el.childNodes);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Add rendered elements.
|
|
757
|
+
if (parent instanceof DocumentFragment)
|
|
758
|
+
el.append(parent);
|
|
759
|
+
else if (parent)
|
|
760
|
+
el.append(...parent.childNodes);
|
|
761
|
+
|
|
762
|
+
// Apply slot children
|
|
763
|
+
if (fragment) {
|
|
764
|
+
for (let slot of el.querySelectorAll('slot[name]')) {
|
|
765
|
+
let name = slot.getAttribute('name');
|
|
766
|
+
if (name)
|
|
767
|
+
slot.append(...fragment.querySelectorAll(`[slot='${name}']`));
|
|
768
|
+
}
|
|
769
|
+
let unamedSlot = el.querySelector('slot:not([name])');
|
|
770
|
+
if (unamedSlot)
|
|
771
|
+
unamedSlot.append(fragment);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
ngm.rootEl = el;
|
|
776
|
+
|
|
777
|
+
// this.rootNg was rendered as childrenOnly=true
|
|
778
|
+
// Apply attributes from a root element to the real root element.
|
|
779
|
+
let ng = ngm.rootNg;
|
|
780
|
+
if (ng.pseudoRoot && ng.pseudoRoot !== el) {
|
|
781
|
+
/*#IFDEV*/assert(el);/*#ENDIF*/
|
|
782
|
+
|
|
783
|
+
// Remove old attributes
|
|
784
|
+
// for (let attrib of this.rootEl.attributes)
|
|
785
|
+
// if (attrib.name !== 'is' && attrib.name !== 'data-style' && !ng.pseudoRoot.hasAttribute(attrib.name))
|
|
786
|
+
// this.rootEl.removeAttribute(attrib.name)
|
|
787
|
+
|
|
788
|
+
// Add/set new attributes
|
|
789
|
+
if (firstTime)
|
|
790
|
+
for (let attrib of ng.pseudoRoot.attributes)
|
|
791
|
+
if (!el.hasAttribute(attrib.name))
|
|
792
|
+
el.setAttribute(attrib.name, attrib.value);
|
|
793
|
+
|
|
794
|
+
// ng.startNode = ng.endNode = this.rootEl;
|
|
795
|
+
// ng.nodesCache = [ng.startNode]
|
|
796
|
+
// for (let path of ng.paths) {
|
|
797
|
+
// if (path.nodeMarker === ng.rootEl)
|
|
798
|
+
// path.nodeMarker = this.rootEl;
|
|
799
|
+
// path.nodesCache = null;
|
|
800
|
+
// /*#IFDEV*/assert(path.nodeBefore !== ng.rootEl)/*#ENDIF*/
|
|
801
|
+
// }
|
|
802
|
+
//
|
|
803
|
+
// ng.rootEl = this.rootEl;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/*#IFDEV*/ngm.rootNg.verify();/*#ENDIF*/
|
|
807
|
+
ngm.reset(); // Mark all NodeGroups as available, for next render.
|
|
808
|
+
/*#IFDEV*/ngm.rootNg.verify();/*#ENDIF*/
|
|
809
|
+
|
|
810
|
+
window.ngm = ngm;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
ngm.mutationWatcherEnabled = true;
|
|
814
|
+
return el;
|
|
815
|
+
//#IFDEV
|
|
816
|
+
//return ngm.modifications;
|
|
817
|
+
//#ENDIF
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
getCloseKey() {
|
|
822
|
+
// Use the joined html when debugging?
|
|
823
|
+
//return '@'+this.html.join('|')
|
|
824
|
+
|
|
825
|
+
return '@'+this.hashedFields[0];
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
622
829
|
/**
|
|
623
830
|
* Path to where an expression should be evaluated within a Shell.
|
|
624
831
|
* Path is only valid until the expressions before it are evaluated.
|
|
@@ -737,6 +944,90 @@ class ExprPath {
|
|
|
737
944
|
this.attrNames = new Set();
|
|
738
945
|
}
|
|
739
946
|
|
|
947
|
+
/**
|
|
948
|
+
*
|
|
949
|
+
* @param expr {Template|Node|Array|function|*}
|
|
950
|
+
* @param newNodes {(Node|Template)[]}
|
|
951
|
+
* @param secondPass {Array} Locations within newNodes to evaluate later. */
|
|
952
|
+
apply(expr, newNodes, secondPass) {
|
|
953
|
+
|
|
954
|
+
if (expr instanceof Template) {
|
|
955
|
+
expr.nodegroup = this.parentNg; // All tests pass w/o this.
|
|
956
|
+
|
|
957
|
+
let ng = this.parentNg.manager.getNodeGroup(expr, true);
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
if (ng) {
|
|
961
|
+
//#IFDEV
|
|
962
|
+
// Make sure the nodeCache of the ExprPath we took it from is sitll valid.
|
|
963
|
+
if (ng.parentPath)
|
|
964
|
+
ng.parentPath.verify();
|
|
965
|
+
//#ENDIF
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
// TODO: Track ranges of changed nodes and only pass those to udomdiff?
|
|
969
|
+
// But will that break the swap benchmark?
|
|
970
|
+
newNodes.push(...ng.getNodes());
|
|
971
|
+
this.nodeGroups.push(ng);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// If expression, evaluate later to find partial match.
|
|
975
|
+
else {
|
|
976
|
+
secondPass.push([newNodes.length, this.nodeGroups.length]);
|
|
977
|
+
newNodes.push(expr);
|
|
978
|
+
this.nodeGroups.push(null); // placeholder
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Node created by an expression.
|
|
983
|
+
else if (expr instanceof Node) {
|
|
984
|
+
|
|
985
|
+
// DocumentFragment created by an expression.
|
|
986
|
+
if (expr instanceof DocumentFragment)
|
|
987
|
+
newNodes.push(...expr.childNodes);
|
|
988
|
+
else
|
|
989
|
+
newNodes.push(expr);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
else if (Array.isArray(expr))
|
|
993
|
+
for (let subExpr of expr)
|
|
994
|
+
this.apply(subExpr, newNodes, secondPass);
|
|
995
|
+
|
|
996
|
+
else if (typeof expr === 'function') {
|
|
997
|
+
let result = expr();
|
|
998
|
+
|
|
999
|
+
this.apply(result, newNodes, secondPass);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Text
|
|
1003
|
+
else {
|
|
1004
|
+
// Convert falsy values (but not 0) to empty string.
|
|
1005
|
+
// Convert numbers to string so they compare the same.
|
|
1006
|
+
let text = (expr === undefined || expr === false || expr === null) ? '' : expr + '';
|
|
1007
|
+
|
|
1008
|
+
// Fast path for updating the text of a single text node.
|
|
1009
|
+
let first = this.nodeBefore.nextSibling;
|
|
1010
|
+
if (first.nodeType === 3 && first.nextSibling === this.nodeMarker && !newNodes.includes(first)) {
|
|
1011
|
+
if (first.textContent !== text)
|
|
1012
|
+
first.textContent = text;
|
|
1013
|
+
|
|
1014
|
+
newNodes.push(first);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
else {
|
|
1018
|
+
// TODO: Optimize this into a Set or Map or something?
|
|
1019
|
+
if (!this.existingTextNodes)
|
|
1020
|
+
this.existingTextNodes = this.getNodes().filter(n => n.nodeType === 3);
|
|
1021
|
+
|
|
1022
|
+
let idx = this.existingTextNodes.findIndex(n => n.textContent === text);
|
|
1023
|
+
if (idx !== -1)
|
|
1024
|
+
newNodes.push(...this.existingTextNodes.splice(idx, 1));
|
|
1025
|
+
else
|
|
1026
|
+
newNodes.push(this.parentNode.ownerDocument.createTextNode(text));
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
740
1031
|
applyMultipleAttribs(node, expr) {
|
|
741
1032
|
/*#IFDEV*/assert(this.type === PathType.Multiple);/*#ENDIF*/
|
|
742
1033
|
|
|
@@ -797,7 +1088,6 @@ class ExprPath {
|
|
|
797
1088
|
func = setValue;
|
|
798
1089
|
args = [expr[0], expr.slice(1), node];
|
|
799
1090
|
node.value = delve(expr[0], expr.slice(1));
|
|
800
|
-
//debugger;
|
|
801
1091
|
}
|
|
802
1092
|
}
|
|
803
1093
|
else
|
|
@@ -918,8 +1208,7 @@ class ExprPath {
|
|
|
918
1208
|
* Clear the nodeCache of this ExprPath, as well as all parent and child ExprPaths that
|
|
919
1209
|
* share the same DOM parent node.
|
|
920
1210
|
*
|
|
921
|
-
* TODO: Is recursive clearing ever necessary?
|
|
922
|
-
*/
|
|
1211
|
+
* TODO: Is recursive clearing ever necessary? */
|
|
923
1212
|
clearNodesCache() {
|
|
924
1213
|
let path = this;
|
|
925
1214
|
|
|
@@ -937,7 +1226,7 @@ class ExprPath {
|
|
|
937
1226
|
|
|
938
1227
|
// Clear cache of child ExprPaths that have the same parentNode
|
|
939
1228
|
for (let ng of path.nodeGroups) {
|
|
940
|
-
if (ng) // Can be null from
|
|
1229
|
+
if (ng) // Can be null from apply()'s push(null) call.
|
|
941
1230
|
for (let path2 of ng.paths) {
|
|
942
1231
|
if (path2.type === PathType.Content && path2.parentNode === parentNode) {
|
|
943
1232
|
path2.nodesCache = null;
|
|
@@ -1205,7 +1494,7 @@ class Shell {
|
|
|
1205
1494
|
|
|
1206
1495
|
// Swap out Embedded Solarite Components with ${} attributes.
|
|
1207
1496
|
// Later, NodeGroup.render() will search for these and replace them with the real components.
|
|
1208
|
-
// Ctrl+F "
|
|
1497
|
+
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
1209
1498
|
if (context === htmlContext.Attribute) {
|
|
1210
1499
|
|
|
1211
1500
|
let lastIndex, lastMatch;
|
|
@@ -1215,7 +1504,7 @@ class Shell {
|
|
|
1215
1504
|
});
|
|
1216
1505
|
|
|
1217
1506
|
if (lastMatch) {
|
|
1218
|
-
let newTagName = lastMatch + '-
|
|
1507
|
+
let newTagName = lastMatch + '-solarite-placeholder';
|
|
1219
1508
|
lastHtml = lastHtml.slice(0, lastIndex) + newTagName + lastHtml.slice(lastIndex + lastMatch.length);
|
|
1220
1509
|
componentNames[lastMatch] = newTagName;
|
|
1221
1510
|
}
|
|
@@ -1232,9 +1521,9 @@ class Shell {
|
|
|
1232
1521
|
|
|
1233
1522
|
// 2. Create elements from html with placeholders.
|
|
1234
1523
|
let template = document.createElement('template'); // Using a single global template won't keep the nodes as children of the DocumentFragment.
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
// Replace '-
|
|
1524
|
+
let joinedHtml = buffer.join('');
|
|
1525
|
+
|
|
1526
|
+
// Replace '-solarite-placeholder' close tags.
|
|
1238
1527
|
// TODO: is there a better way? What if the close tag is inside a comment?
|
|
1239
1528
|
for (let name in componentNames)
|
|
1240
1529
|
joinedHtml = joinedHtml.replaceAll(`</${name}>`, `</${componentNames[name]}>`);
|
|
@@ -1257,7 +1546,7 @@ class Shell {
|
|
|
1257
1546
|
|
|
1258
1547
|
// Replace attributes
|
|
1259
1548
|
if (node.nodeType === 1) {
|
|
1260
|
-
for (let attr of node.attributes) {
|
|
1549
|
+
for (let attr of [...node.attributes]) { // Copy the attributes array b/c we remove attributes as we go.
|
|
1261
1550
|
|
|
1262
1551
|
// Whole attribute
|
|
1263
1552
|
let matches = attr.name.match(/^[\ue000-\uf8ff]$/);
|
|
@@ -1359,8 +1648,8 @@ class Shell {
|
|
|
1359
1648
|
}
|
|
1360
1649
|
toRemove.map(el => el.remove());
|
|
1361
1650
|
|
|
1362
|
-
// Handle
|
|
1363
|
-
// Ctrl+F "
|
|
1651
|
+
// Handle solarite-placeholder's.
|
|
1652
|
+
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
1364
1653
|
//if (componentNames.size)
|
|
1365
1654
|
// this.components = [...this.fragment.querySelectorAll([...componentNames].join(','))]
|
|
1366
1655
|
|
|
@@ -1455,87 +1744,6 @@ class Shell {
|
|
|
1455
1744
|
|
|
1456
1745
|
let shells = new WeakMap();
|
|
1457
1746
|
|
|
1458
|
-
/**
|
|
1459
|
-
* The html strings and evaluated expressions from an html tagged template.
|
|
1460
|
-
* A unique Template is created for each item in a loop.
|
|
1461
|
-
* Although the reference to the html strings is shared among templates. */
|
|
1462
|
-
class Template {
|
|
1463
|
-
|
|
1464
|
-
/** @type {(Template|string|function)|(Template|string|function)[]} Evaulated expressions. */
|
|
1465
|
-
exprs = []
|
|
1466
|
-
|
|
1467
|
-
/** @type {string[]} */
|
|
1468
|
-
html = [];
|
|
1469
|
-
|
|
1470
|
-
/**
|
|
1471
|
-
* If true, use this template to replace an existing element, instead of appending children to it.
|
|
1472
|
-
* @type {?boolean} */
|
|
1473
|
-
replaceMode;
|
|
1474
|
-
|
|
1475
|
-
/** Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
|
|
1476
|
-
hashedFields;
|
|
1477
|
-
|
|
1478
|
-
/**
|
|
1479
|
-
* @deprecated
|
|
1480
|
-
* @type {ExprPath} Used with forEach() from watch.js
|
|
1481
|
-
* Set in NodeGroup.applyOneExpr() */
|
|
1482
|
-
parentPath;
|
|
1483
|
-
|
|
1484
|
-
/** @type {NodeGroup} */
|
|
1485
|
-
nodeGroup;
|
|
1486
|
-
|
|
1487
|
-
/**
|
|
1488
|
-
* @type {string[][]} */
|
|
1489
|
-
paths = [];
|
|
1490
|
-
|
|
1491
|
-
/**
|
|
1492
|
-
*
|
|
1493
|
-
* @param htmlStrings {string[]}
|
|
1494
|
-
* @param exprs {*[]} */
|
|
1495
|
-
constructor(htmlStrings, exprs) {
|
|
1496
|
-
this.html = htmlStrings;
|
|
1497
|
-
this.exprs = exprs;
|
|
1498
|
-
|
|
1499
|
-
//this.trace = new Error().stack.split(/\n/g)
|
|
1500
|
-
|
|
1501
|
-
// Multiple templates can share the same htmlStrings array.
|
|
1502
|
-
//this.hashedFields = [getObjectId(htmlStrings), exprs]
|
|
1503
|
-
|
|
1504
|
-
//#IFDEV
|
|
1505
|
-
assert(Array.isArray(htmlStrings));
|
|
1506
|
-
assert(Array.isArray(exprs));
|
|
1507
|
-
|
|
1508
|
-
Object.defineProperty(this, 'debug', {
|
|
1509
|
-
get() {
|
|
1510
|
-
return JSON.stringify([this.html, this.exprs]);
|
|
1511
|
-
}
|
|
1512
|
-
});
|
|
1513
|
-
//#ENDIF
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
/**
|
|
1517
|
-
* Called by JSON.serialize when it encounters a Template.
|
|
1518
|
-
* This prevents the hashed version from being too large. */
|
|
1519
|
-
toJSON() {
|
|
1520
|
-
if (!this.hashedFields)
|
|
1521
|
-
this.hashedFields = [getObjectId(this.html, 'Html'), this.exprs];
|
|
1522
|
-
|
|
1523
|
-
return this.hashedFields
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
toNode() {
|
|
1527
|
-
let ngm = new NodeGroupManager();
|
|
1528
|
-
return ngm.render(this);
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
getCloseKey() {
|
|
1532
|
-
// Use the joined html when debugging?
|
|
1533
|
-
//return '@'+this.html.join('|')
|
|
1534
|
-
|
|
1535
|
-
return '@'+this.hashedFields[0];
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
1747
|
/**
|
|
1540
1748
|
* ISC License
|
|
1541
1749
|
*
|
|
@@ -1755,734 +1963,16 @@ const udomdiff = (parentNode, a, b, before) => {
|
|
|
1755
1963
|
parentNode.removeChild(aNode);
|
|
1756
1964
|
|
|
1757
1965
|
//#IFDEV
|
|
1758
|
-
if (aNode instanceof NodeGroup)
|
|
1759
|
-
aNode.verify();
|
|
1760
|
-
// if (parentNode instanceof ExprPath)
|
|
1761
|
-
// parentNode.verify();
|
|
1762
|
-
//#ENDIF
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
return b;
|
|
1767
|
-
};
|
|
1768
|
-
|
|
1769
|
-
/**
|
|
1770
|
-
* Tools for watch variables and performing precise renders.
|
|
1771
|
-
*/
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
/**
|
|
1775
|
-
* Stores info how to transform a path to a template. */
|
|
1776
|
-
class TransformerInfo {
|
|
1777
|
-
constructor(path, transformer, hash) {
|
|
1778
|
-
this.path = path;
|
|
1779
|
-
this.transformer = transformer;
|
|
1780
|
-
this.hash = hash;
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
/**
|
|
1786
|
-
* Maps an object path to the function that converts it to a Template.
|
|
1787
|
-
* Once it's convert to a template, we can get the hash of that Tempate.
|
|
1788
|
-
* Then that hash tells us what NodeGroups are affected by the object.
|
|
1789
|
-
*
|
|
1790
|
-
* We store the function to get the Template, instead of the Template itself,
|
|
1791
|
-
* so we can call that function again when the object has a new value.
|
|
1792
|
-
* @type {MultiValueMap<Object, function(...Object):Template>} */
|
|
1793
|
-
let pathToTransformer = new MultiValueMap(); // uses a Set() for each value.
|
|
1794
|
-
|
|
1795
|
-
/**
|
|
1796
|
-
*
|
|
1797
|
-
* @param objectPaths {(*|function)[]}
|
|
1798
|
-
* @returns {Template} */
|
|
1799
|
-
function watchGet(...objectPaths) {
|
|
1800
|
-
|
|
1801
|
-
/** @type {function} */
|
|
1802
|
-
let transformer = objectPaths.at(-1);
|
|
1803
|
-
let paths;
|
|
1804
|
-
if (typeof transformer === 'function') {
|
|
1805
|
-
paths = objectPaths.slice(0, -1);
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
// No transformer provided, so we create our own.
|
|
1809
|
-
else if (objectPaths.length === 1) {
|
|
1810
|
-
paths = [objectPaths[0].slice(0, -1)];
|
|
1811
|
-
let prop = objectPaths[0].at(-1);
|
|
1812
|
-
transformer = (...args) => (args[0][prop]);
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
// Save arguments used to call the template, so we can call it again when those args have their values change.
|
|
1817
|
-
let args = [];
|
|
1818
|
-
for (let path of paths) {
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
let obj = delve(watchSet(path[0]), path.slice(1));
|
|
1822
|
-
args.push(obj);
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
let template = transformer(...args);
|
|
1826
|
-
|
|
1827
|
-
// If the result isn't a Template, convert the function to return a Template that wraps the result.
|
|
1828
|
-
// This way NodeGroupManager.findAndDelete() can find a NodeGroup that matches this Template's hash.
|
|
1829
|
-
if (!(template instanceof Template)) {
|
|
1830
|
-
let oldToTemplate = transformer;
|
|
1831
|
-
transformer = function() {
|
|
1832
|
-
return new Template(['', ''], [oldToTemplate(...arguments)]);
|
|
1833
|
-
};
|
|
1834
|
-
template = transformer(...args);
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
// Map the object paths to the function that creates a template.
|
|
1838
|
-
let hash = getObjectHash(template);
|
|
1839
|
-
for (let path of paths) {
|
|
1840
|
-
let serializedPath = serializePath(path);
|
|
1841
|
-
pathToTransformer.add(serializedPath, new TransformerInfo(path, transformer, hash)); // Uses a Set() to ensure no duplicates.
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
return template;
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
//let proxyCache = new WeakMap();
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
/**
|
|
1851
|
-
* Set the value of a variable in a way that's watched, so later when we call .renderWatched()
|
|
1852
|
-
* We can find what NodeGroups to update.*/
|
|
1853
|
-
function watchSet(obj) {
|
|
1854
|
-
if (obj?.$isProxy===true)
|
|
1855
|
-
return obj; // It's already a Proxy.
|
|
1856
|
-
|
|
1857
|
-
// This cache doesn't make things faster.
|
|
1858
|
-
// let result = proxyCache.get(obj);
|
|
1859
|
-
// if (!result) {
|
|
1860
|
-
// result = new Proxy(obj, new ProxyHandler(obj));
|
|
1861
|
-
// proxyCache.set(obj, result);
|
|
1862
|
-
// }
|
|
1863
|
-
// return result;
|
|
1864
|
-
return new Proxy(obj, new ProxyHandler$1(obj));
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
/**
|
|
1868
|
-
* Loop over each item and apply watchGet() to each item.
|
|
1869
|
-
* @param arrayPath {*[]}
|
|
1870
|
-
* @param callback {function(obj:Object, index:int):Template}
|
|
1871
|
-
* @returns {Template} */
|
|
1872
|
-
function forEach(arrayPath, callback) {
|
|
1873
|
-
let array = delve(arrayPath[0], arrayPath.slice(1));
|
|
1874
|
-
|
|
1875
|
-
// This is retrieved on the 'insert' path inside renderWatched()
|
|
1876
|
-
let ngm = NodeGroupManager.get(arrayPath[0]);
|
|
1877
|
-
if (ngm.clearSubscribers) {
|
|
1878
|
-
ngm.clearSubscribers = false;
|
|
1879
|
-
ngm.pathToLoopInfo = new MultiValueMap();
|
|
1880
|
-
} // TODO: Move tis into NodeGroupMAnager.get() without breaking things?
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
let newItems = [...array.map((item, i) => {
|
|
1884
|
-
// TODO: This needs to wrap callback so we can pass it the index also.
|
|
1885
|
-
return watchGet([...arrayPath, i], callback); // calls callback(array[i], i)
|
|
1886
|
-
})
|
|
1887
|
-
];
|
|
1888
|
-
|
|
1889
|
-
// We return a template that wraps the array
|
|
1890
|
-
// So that NodeGroup.applyOneExpr can set the ExprPath and nextSibling on the template.
|
|
1891
|
-
// Then the 'insert' path in renderWatched() uses that data fora dding more nodes.
|
|
1892
|
-
let result = new Template(['', ''], [newItems]);
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
// We get a unique hash for each foreach template because the [''] array is unique each time.
|
|
1896
|
-
let loopInfo = new LoopInfo(result, callback);
|
|
1897
|
-
ngm.pathToLoopInfo.add(serializePath(arrayPath), loopInfo);
|
|
1898
|
-
return result;
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
function serializePath(path) {
|
|
1902
|
-
// Convert any array indices to strings, so serialized comparisons work.
|
|
1903
|
-
return JSON.stringify([getObjectId(path[0]), ...path.slice(1).map(item => item+'')])
|
|
1904
|
-
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
/**
|
|
1909
|
-
* When an object property is accessed, a new Proxy with a new instance of this handler class is created,
|
|
1910
|
-
* but it tracks the path from the root to the property.
|
|
1911
|
-
* That way when a property is set, it can report the changed path. */
|
|
1912
|
-
class ProxyHandler$1 {
|
|
1913
|
-
|
|
1914
|
-
/**
|
|
1915
|
-
* @param root An element managed by a NodeGroupManager. The same as the NodeGroupManager's rootEl.
|
|
1916
|
-
* @param path {string[]} Used internally. */
|
|
1917
|
-
constructor(root, path=[]) {
|
|
1918
|
-
/*#IFDEV*/assert(NodeGroupManager.get(root));/*#ENDIF*/
|
|
1919
|
-
this.root = root;
|
|
1920
|
-
this.path = path; // path from root to this Proxy.
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
/**
|
|
1924
|
-
* @param obj {Object}
|
|
1925
|
-
* @param prop {string} */
|
|
1926
|
-
get(obj, prop) {
|
|
1927
|
-
|
|
1928
|
-
// Special props. Currently unused.
|
|
1929
|
-
// if (prop === '$path')
|
|
1930
|
-
// return this.path;
|
|
1931
|
-
// if (prop === '$root')
|
|
1932
|
-
// return this.root;
|
|
1933
|
-
if (prop === '$isProxy')
|
|
1934
|
-
return true;
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
// 1. Array.splice()
|
|
1938
|
-
if (prop === 'splice' && Array.isArray(obj)) {
|
|
1939
|
-
return (index, deleteCount, ...items) => {
|
|
1940
|
-
let ngm = NodeGroupManager.get(this.root);
|
|
1941
|
-
|
|
1942
|
-
if (deleteCount) {
|
|
1943
|
-
|
|
1944
|
-
// Get the hash of each object along the delete range. The process to get the hash is:
|
|
1945
|
-
// Serialized Path -> transformer -> Template -> hash.
|
|
1946
|
-
let hashes = [];
|
|
1947
|
-
for (let i=index; i<index+deleteCount; i++) {
|
|
1948
|
-
let serializedPath = serializePath([this.root, ...this.path, i+'']);
|
|
1949
|
-
|
|
1950
|
-
let obj = delve(this.root, [...this.path, i]);
|
|
1951
|
-
for (let transformerInfo of pathToTransformer.getAll(serializedPath)) {
|
|
1952
|
-
let template = transformerInfo.transformer(obj);
|
|
1953
|
-
let hash = getObjectHash(template);
|
|
1954
|
-
hashes.push(hash); // Hashes may go to nodes in more than one loop.
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
ngm.changes.push(new Change('delete', this.root, [...this.path, index+''], hashes));
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
//let oldArray = [...obj];
|
|
1962
|
-
let result = obj.splice(index, deleteCount);
|
|
1963
|
-
|
|
1964
|
-
// Inserting
|
|
1965
|
-
if (items.length) {
|
|
1966
|
-
|
|
1967
|
-
let beforeNgs;
|
|
1968
|
-
for (let loopInfo of ngm.getLoopInfo([this.root, ...this.path])) {
|
|
1969
|
-
let beforeObj = delve(this.root, [...this.path, index]);
|
|
1970
|
-
|
|
1971
|
-
// Find where to insert before.
|
|
1972
|
-
if (beforeObj) {
|
|
1973
|
-
let beforeTemplate = loopInfo.itemTransformer(beforeObj);
|
|
1974
|
-
let beforeHash = getObjectHash(beforeTemplate);
|
|
1975
|
-
beforeNgs = ngm.nodeGroupsAvailable.data[beforeHash];
|
|
1976
|
-
|
|
1977
|
-
if (beforeNgs) {
|
|
1978
|
-
let hash = getObjectHash(loopInfo.template);
|
|
1979
|
-
let loopNgs = ngm.nodeGroupsAvailable.data[hash] || [];
|
|
1980
|
-
for (let loopNg of loopNgs)
|
|
1981
|
-
for (let beforeNg of beforeNgs)
|
|
1982
|
-
if (beforeNg.startNode.parentNode === loopNg.startNode.parentNode)
|
|
1983
|
-
ngm.changes.push(new Change('insert', this.root, [...this.path, index + ''], items, beforeTemplate));
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
if (!beforeNgs)
|
|
1987
|
-
ngm.changes.push(new Change('insert', this.root, [...this.path, index + ''], items));
|
|
1988
|
-
}
|
|
1989
|
-
obj.splice(index, 0, ...items);
|
|
1990
|
-
}
|
|
1991
|
-
return result;
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
// 2. Get property
|
|
1998
|
-
// If we're getting an object or array property, apply watch() to it recursively.
|
|
1999
|
-
let result = Reflect.get(obj, prop);
|
|
2000
|
-
if (result && typeof result === 'object') {
|
|
2001
|
-
let handler = new ProxyHandler$1(this.root, [...this.path, prop]); // same root, one level deeper on the path.
|
|
2002
|
-
return new Proxy(result, handler);
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
return result;
|
|
2006
|
-
}
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
set(obj, prop, newValue) {
|
|
2010
|
-
let ngm = NodeGroupManager.get(this.root);
|
|
2011
|
-
ngm.changes.push(new Change('set', this.root, [...this.path, prop], newValue));
|
|
2012
|
-
return Reflect.set(obj, prop, newValue)
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
/**
|
|
2018
|
-
*
|
|
2019
|
-
*/
|
|
2020
|
-
class Change {
|
|
2021
|
-
|
|
2022
|
-
/**
|
|
2023
|
-
* @param action {string}
|
|
2024
|
-
* @param root {Object|Array}
|
|
2025
|
-
* @param path {string[]}
|
|
2026
|
-
* @param value
|
|
2027
|
-
* If setting a value, this is the new value.
|
|
2028
|
-
* If deleting from an array, this is an array of all the NodeGroups to delete.
|
|
2029
|
-
*
|
|
2030
|
-
* @param beforeTemplate
|
|
2031
|
-
* */
|
|
2032
|
-
constructor(action, root, path, value, beforeTemplate=null) {
|
|
2033
|
-
this.action = action;
|
|
2034
|
-
|
|
2035
|
-
// TODO: Store root as first item of path, to be consistent with code elsewhere.
|
|
2036
|
-
this.root = root;
|
|
2037
|
-
this.path = path;
|
|
2038
|
-
this.value = value;
|
|
2039
|
-
this.beforeTemplate = beforeTemplate;
|
|
2040
|
-
|
|
2041
|
-
/** @type {TransformerInfo[]} */
|
|
2042
|
-
this.transformerInfo = [];
|
|
2043
|
-
|
|
2044
|
-
// Traverse up the path.
|
|
2045
|
-
for (let i=this.path.length; i>0; i--) {
|
|
2046
|
-
let path = this.path.slice(0, i);
|
|
2047
|
-
let fullPath = [this.root, ...path];
|
|
2048
|
-
|
|
2049
|
-
let serializedPath = getObjectHash(fullPath); // TODO: Why not serializedPath() ?
|
|
2050
|
-
this.transformerInfo.push(...pathToTransformer.getAll(serializedPath));
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
let logGets = false;
|
|
2056
|
-
let gets = [];
|
|
2057
|
-
|
|
2058
|
-
let withinSet = 0;
|
|
2059
|
-
|
|
2060
|
-
/**
|
|
2061
|
-
* Turn the props on obj into JavasCript properties that return Proxies when accessed.
|
|
2062
|
-
* If called more than once, return the already-converted object.
|
|
2063
|
-
* @param obj {Object}
|
|
2064
|
-
* @param props {string}
|
|
2065
|
-
* @returns {*|{$proxyHandler}}
|
|
2066
|
-
*/
|
|
2067
|
-
function watch(obj, ...props) {
|
|
2068
|
-
|
|
2069
|
-
if (props.length) {
|
|
2070
|
-
let internalProps = {};
|
|
2071
|
-
for (let prop of props) {
|
|
2072
|
-
internalProps[prop] = obj[prop];
|
|
2073
|
-
Object.defineProperty(obj, prop, {
|
|
2074
|
-
get() {
|
|
2075
|
-
return new Proxy(obj, new ProxyHandler(obj, [], internalProps))[prop];
|
|
2076
|
-
},
|
|
2077
|
-
set(value) {
|
|
2078
|
-
return watch(this)[prop] = value;
|
|
2079
|
-
}
|
|
2080
|
-
});
|
|
2081
|
-
}
|
|
2082
|
-
return;
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
if (obj?.$proxyHandler)
|
|
2087
|
-
return obj; // It's already a Proxy.
|
|
2088
|
-
|
|
2089
|
-
// This cache doesn't make things faster.
|
|
2090
|
-
// But could it save memory?
|
|
2091
|
-
// let result = proxyCache.get(obj);
|
|
2092
|
-
// if (!result) {
|
|
2093
|
-
// result = new Proxy(obj, new ProxyHandler(obj));
|
|
2094
|
-
// proxyCache.set(obj, result);
|
|
2095
|
-
// }
|
|
2096
|
-
// return result;
|
|
2097
|
-
/*#IFDEV*/assert(!obj.$proxyHandler);/*#ENDIF*/
|
|
2098
|
-
return new Proxy(obj, new ProxyHandler(obj));
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
/**
|
|
2102
|
-
* Provides methods used when a Proxied version of a property is accessed on an object returned by watch() */
|
|
2103
|
-
class ProxyHandler {
|
|
2104
|
-
|
|
2105
|
-
serializedPath;
|
|
2106
|
-
|
|
2107
|
-
/**
|
|
2108
|
-
* @param root An element managed by a NodeGroupManager. The same as the NodeGroupManager's rootEl.
|
|
2109
|
-
* @param path {string[]} Used internally.
|
|
2110
|
-
* @param props */
|
|
2111
|
-
constructor(root, path=[], props=null) {
|
|
2112
|
-
/*#IFDEV*/assert(NodeGroupManager.get(root));/*#ENDIF*/
|
|
2113
|
-
this.root = root;
|
|
2114
|
-
this.path = path; // path from root to this Proxy.
|
|
2115
|
-
this.props = props;
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
/**
|
|
2119
|
-
* Get the full path to this property from the root watched object.
|
|
2120
|
-
* @param atIndex
|
|
2121
|
-
* @returns {string} */
|
|
2122
|
-
getSerializedPath(atIndex=null) {
|
|
2123
|
-
if (!this.serializedPath)
|
|
2124
|
-
this.serializedPath = JSON.stringify([getObjectId(this.root), ...this.path.map(item => item + '')]);
|
|
2125
|
-
|
|
2126
|
-
if (atIndex!== null)
|
|
2127
|
-
return this.serializedPath.slice(0, -1) + ',"' + atIndex + '"]';
|
|
2128
|
-
return this.serializedPath;
|
|
2129
|
-
}
|
|
2130
|
-
|
|
2131
|
-
/**
|
|
2132
|
-
* Return a ProxyHandler for a property one level deeper at pathItem.
|
|
2133
|
-
* @param pathItem {string}
|
|
2134
|
-
* @returns {ProxyHandler} */
|
|
2135
|
-
extend(pathItem) {
|
|
2136
|
-
pathItem += '';
|
|
2137
|
-
assert(!this.root.$proxyHandler);
|
|
2138
|
-
let result = new ProxyHandler(this.root, [...this.path, pathItem], this.props);
|
|
2139
|
-
if (this.serializedPath)
|
|
2140
|
-
result.serializedPath = this.getSerializedPath(pathItem);
|
|
2141
|
-
|
|
2142
|
-
return result;
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
|
-
/**
|
|
2146
|
-
* Called directly by JavaScript when accessing the value of a property.
|
|
2147
|
-
* @param obj {Object}
|
|
2148
|
-
* @param prop {string} */
|
|
2149
|
-
get(obj, prop) {
|
|
2150
|
-
|
|
2151
|
-
// 1. Special props.
|
|
2152
|
-
if (prop === '$proxyHandler')
|
|
2153
|
-
return this;
|
|
2154
|
-
else if (prop === '$removeProxy')
|
|
2155
|
-
return delve(this.props || this.root, this.path);
|
|
2156
|
-
|
|
2157
|
-
// 2. Array functions.
|
|
2158
|
-
else if (prop === 'map' && Array.isArray(obj)) {
|
|
2159
|
-
|
|
2160
|
-
let ngm = NodeGroupManager.get(this.root);
|
|
2161
|
-
ngm.clearSubscribersIfNeeded();
|
|
2162
|
-
|
|
2163
|
-
return callback => {
|
|
2164
|
-
let loopInfo;
|
|
2165
|
-
|
|
2166
|
-
let children = [];
|
|
2167
|
-
let transformer = obj => {
|
|
2168
|
-
let templates = [];
|
|
2169
|
-
|
|
2170
|
-
for (let i = 0; i < obj.length; i++) {
|
|
2171
|
-
|
|
2172
|
-
// Watch obj[i].
|
|
2173
|
-
let handler = this.extend(i);
|
|
2174
|
-
let item = obj[i];
|
|
2175
|
-
if (!item.$proxyHandler)
|
|
2176
|
-
item = new Proxy(item, handler);
|
|
2177
|
-
|
|
2178
|
-
let template = callback(item, i, obj);
|
|
2179
|
-
templates.push(template);
|
|
2180
|
-
|
|
2181
|
-
// If the loop is re-evaluted via Set() then we add duplicate TemplateInfo's
|
|
2182
|
-
//if (!withinSet) {
|
|
2183
|
-
let spath = this.getSerializedPath(i);
|
|
2184
|
-
let subscriber = new Subscriber(callback, template);
|
|
2185
|
-
subscriber.parent = loopInfo;
|
|
2186
|
-
ngm.subscribers.add(spath, subscriber);
|
|
2187
|
-
children.push([spath, subscriber]);
|
|
2188
|
-
//}
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
// A parent Template that surrounds all the items in the loop.
|
|
2192
|
-
// This lets us get template.nodeGroup.endNode so we can insertBefore().
|
|
2193
|
-
return new Template(['', ''], [templates]);
|
|
2194
|
-
};
|
|
2195
|
-
|
|
2196
|
-
if (!withinSet)
|
|
2197
|
-
loopInfo = new Subscriber(transformer, null, callback);
|
|
2198
|
-
let wholeLoopTemplate = transformer(obj);
|
|
2199
|
-
if (!withinSet) {
|
|
2200
|
-
loopInfo.template = wholeLoopTemplate;
|
|
2201
|
-
loopInfo.children = children;
|
|
2202
|
-
ngm.subscribers.add(this.getSerializedPath(), loopInfo);
|
|
2203
|
-
}
|
|
2204
|
-
return wholeLoopTemplate;
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
else if ((prop ==='splice' || prop === 'fastSplice') && Array.isArray(obj)) {
|
|
2209
|
-
let ngm = NodeGroupManager.get(this.root);
|
|
2210
|
-
return (index, deleteCount, ...items) => {
|
|
2211
|
-
let diff = items.length - deleteCount;
|
|
2212
|
-
let objLength = obj.length;
|
|
2213
|
-
|
|
2214
|
-
// Delete
|
|
2215
|
-
if (deleteCount) {
|
|
2216
|
-
for (let i=index; i<index+deleteCount; i++) {
|
|
2217
|
-
|
|
2218
|
-
// Update pathToTemplates
|
|
2219
|
-
let spath = this.getSerializedPath(i);
|
|
2220
|
-
|
|
2221
|
-
// Delete nodes of associated NodeGroups.
|
|
2222
|
-
for (let subscriber of ngm.subscribers.data[spath] || []) {
|
|
2223
|
-
let ng = subscriber.template.nodeGroup;
|
|
2224
|
-
for (let node of ng.getNodes())
|
|
2225
|
-
node.remove();
|
|
2226
|
-
|
|
2227
|
-
// Delete NodeGroup from NodeGroupManager.
|
|
2228
|
-
ngm.nodeGroupsAvailable.delete(ng.exactKey, ng);
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
delete ngm.subscribers.data[spath]; // Deletes templates associated with every loop where this is used.
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
|
|
2235
|
-
// Update indices of subsequent items.
|
|
2236
|
-
if (diff) {
|
|
2237
|
-
let loopPath = this.getSerializedPath();
|
|
2238
|
-
let loopInfo = [...ngm.subscribers.getAll(loopPath)][0]; // TODO: Handle multiple loops.
|
|
2239
|
-
|
|
2240
|
-
let move = (oldIndex) => {
|
|
2241
|
-
let newIndex = oldIndex+diff;
|
|
2242
|
-
let oldPath = this.getSerializedPath(oldIndex);
|
|
2243
|
-
let newPath = this.getSerializedPath(newIndex);
|
|
2244
|
-
|
|
2245
|
-
let subscribers = ngm.subscribers.data[oldPath];
|
|
2246
|
-
delete ngm.subscribers.data[oldPath]; // TODO: Some can be overwritten w/o being deleted?
|
|
2247
|
-
ngm.subscribers.data[newPath] = subscribers;
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
// Update associated NodeGroups by passing them newIndex.
|
|
2251
|
-
// This is unnecessary for most loops since they don't use the index.
|
|
2252
|
-
// fastSplice skips this path, it skips updating item indices.
|
|
2253
|
-
if (prop === 'splice') {
|
|
2254
|
-
let array = delve(this.root, this.path);
|
|
2255
|
-
for (let subscriber of subscribers) {
|
|
2256
|
-
let ng = subscriber.template.nodeGroup;
|
|
2257
|
-
|
|
2258
|
-
let item = array[oldIndex];
|
|
2259
|
-
|
|
2260
|
-
//assert(!item.$proxyHandler)
|
|
2261
|
-
let proxyItem = getProxy(item, this, this.path, newIndex); //new Proxy(item, this.extend(newIndex));
|
|
2262
|
-
let exprs = loopInfo.itemTransformer(proxyItem, newIndex).exprs; // TODO: Pass updated array as third argument to transformer.
|
|
2263
|
-
ng.applyExprs(exprs); // this is the slow part.
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
};
|
|
2267
|
-
|
|
2268
|
-
// Iterate in different directions depending on whether diff is positive or negative.
|
|
2269
|
-
if (diff > 0) // Moving items to the right, so we iterate backward from the end.
|
|
2270
|
-
for (let i = objLength-1; i >= index + items.length + deleteCount; i--)
|
|
2271
|
-
move(i);
|
|
2272
|
-
|
|
2273
|
-
else // Moving items to the left, so we iterate forward.
|
|
2274
|
-
for (let i = index + items.length + deleteCount; i < objLength; i++)
|
|
2275
|
-
move(i);
|
|
2276
|
-
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
// Add new items
|
|
2280
|
-
if (items.length) {
|
|
2281
|
-
let loopPath = this.getSerializedPath();
|
|
2282
|
-
|
|
2283
|
-
let beforePath = this.getSerializedPath(index);
|
|
2284
|
-
let ngm = NodeGroupManager.get(this.root);
|
|
2285
|
-
|
|
2286
|
-
for (let loopInfo of ngm.subscribers.getAll(loopPath)) {
|
|
2287
|
-
let beforeNodes = index < objLength - deleteCount
|
|
2288
|
-
? [...ngm.subscribers.getAll(beforePath)].map(t => t.template.nodeGroup.startNode)
|
|
2289
|
-
: [loopInfo.template.nodeGroup.endNode];
|
|
2290
|
-
for (let beforeNode of beforeNodes) { // TODO: Need to match the beforeNg with the loopInfo instead of iterating.
|
|
2291
|
-
for (let i = 0; i < items.length; i++) {
|
|
2292
|
-
|
|
2293
|
-
// Create NodeGroup of new item.
|
|
2294
|
-
let itemHandler = this.extend(index + i);
|
|
2295
|
-
assert(!items[i].$proxyHandler);
|
|
2296
|
-
let proxyItem = new Proxy(items[i], itemHandler);
|
|
2297
|
-
let template = loopInfo.itemTransformer(proxyItem);
|
|
2298
|
-
let ng = ngm.getNodeGroup(template, null, true);
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
for (let node of ng.getNodes()) {
|
|
2302
|
-
beforeNode.parentNode.insertBefore(node, beforeNode);
|
|
2303
|
-
//loopInfo.template.nodeGroup.endNode = node; // The loop's end node is actually an empty text node, so don't do this.
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
// Add new items to ngm.templateInfo
|
|
2308
|
-
let spath = itemHandler.getSerializedPath();
|
|
2309
|
-
let subscriber = new Subscriber(loopInfo.itemTransformer, template);
|
|
2310
|
-
subscriber.parent = loopInfo;
|
|
2311
|
-
ngm.subscribers.add(spath, subscriber);
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
loopInfo.template.nodeGroup.parentPath.clearNodesCache();
|
|
2315
|
-
loopInfo.template.nodeGroup.nodesCache = null;
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
|
|
2319
|
-
let result = obj.splice(index, deleteCount, ...items);
|
|
2320
|
-
|
|
2321
|
-
//this.notify(this.path);
|
|
2322
|
-
|
|
2323
|
-
return result;
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
|
|
2327
|
-
// Allow these functions to use proxied objects as arguments.
|
|
2328
|
-
else if (prop ==='indexOf' && Array.isArray(obj)) {
|
|
2329
|
-
return item => {
|
|
2330
|
-
return obj.indexOf(item.$removeProxy || item)
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
|
-
// 3. Get property
|
|
2335
|
-
else {
|
|
2336
|
-
|
|
2337
|
-
let obj2 = obj === this.root && this.props ? this.props : obj;
|
|
2338
|
-
let result = Reflect.get(obj2, prop);
|
|
2339
|
-
|
|
2340
|
-
// This is read by watchFunction() which is called in NodeGroup.applyOneExpr().
|
|
2341
|
-
// It's used to see what variables contribute to an expression.
|
|
2342
|
-
if (logGets)
|
|
2343
|
-
gets.push([this.root, ...this.path, prop]);
|
|
2344
|
-
|
|
2345
|
-
// If we're getting an object or array property, apply watch() to it recursively.
|
|
2346
|
-
if (result && typeof result === 'object') {
|
|
2347
|
-
let handler = this.extend(prop); // same root, one level deeper on the path.
|
|
2348
|
-
/*#IFDEV*/assert(!result.$proxyHandler);/*#ENDIF*/
|
|
2349
|
-
return new Proxy(result, handler);
|
|
2350
|
-
}
|
|
2351
|
-
|
|
2352
|
-
return result;
|
|
2353
|
-
}
|
|
2354
|
-
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
/**
|
|
2358
|
-
* Called directly by JavaScript when setting the value of a property via equals.
|
|
2359
|
-
* @param obj
|
|
2360
|
-
* @param prop
|
|
2361
|
-
* @param value
|
|
2362
|
-
* @returns {boolean} */
|
|
2363
|
-
set(obj, prop, value) {
|
|
2364
|
-
withinSet++;
|
|
2365
|
-
|
|
2366
|
-
let obj2 = obj === this.root && this.props ? this.props : obj;
|
|
2367
|
-
let result = Reflect.set(obj2, prop, value);
|
|
2368
|
-
let fullPath = [...this.path, prop+''];
|
|
2369
|
-
this.notify(fullPath);
|
|
2370
|
-
|
|
2371
|
-
withinSet --;
|
|
2372
|
-
|
|
2373
|
-
return result;
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
|
-
/**
|
|
2377
|
-
* Find every subscriber for fullPath, and above, and call applyExprs() for it.
|
|
2378
|
-
* @param fullPath {string[]}
|
|
2379
|
-
* @param excluded {Set} */
|
|
2380
|
-
notify(fullPath, excluded = new Set()) {
|
|
2381
|
-
|
|
2382
|
-
// Traverse upward through the path, looking for pathToTemplates.
|
|
2383
|
-
let ngm = NodeGroupManager.get(this.root);
|
|
2384
|
-
|
|
2385
|
-
let rootHash = getObjectId(this.root);
|
|
2386
|
-
|
|
2387
|
-
let len = fullPath.length;
|
|
2388
|
-
while (len >= 1) {
|
|
2389
|
-
let path = fullPath.slice(0, len);
|
|
2390
|
-
let val = delve(this.root, path);
|
|
2391
|
-
let serializedPath = JSON.stringify([rootHash, ...path]);
|
|
2392
|
-
for (let subscriber of ngm.subscribers.getAll(serializedPath)) {
|
|
2393
|
-
// We already applied expressions for a single item within this loop.
|
|
2394
|
-
if (excluded.has(subscriber))
|
|
2395
|
-
continue;
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
// Delete child subscriptions so we don't have duplicate subscriptions when we call applyExprs() directly below.
|
|
2399
|
-
if (subscriber.children) {
|
|
2400
|
-
for (let [spath, childInfo] of subscriber.children)
|
|
2401
|
-
ngm.subscribers.delete(spath, childInfo);
|
|
2402
|
-
subscriber.children = undefined;
|
|
2403
|
-
}
|
|
2404
|
-
|
|
2405
|
-
let proxyVal = getProxy(val, this, path);
|
|
2406
|
-
let exprs = subscriber.transformer(proxyVal).exprs; // TODO: Pass updated array as third argument to transformer.
|
|
2407
|
-
for (let path of subscriber.template.nodeGroup.paths)
|
|
2408
|
-
path.clearNodesCache();
|
|
2409
|
-
|
|
2410
|
-
// Apply expressions.
|
|
2411
|
-
subscriber.template.nodeGroup.applyExprs(exprs);
|
|
2412
|
-
|
|
2413
|
-
// Don't also process parent loop after updating a single item within it.
|
|
2414
|
-
if (subscriber.parent)
|
|
2415
|
-
excluded.add(subscriber.parent);
|
|
1966
|
+
if (aNode instanceof NodeGroup)
|
|
1967
|
+
aNode.verify();
|
|
1968
|
+
// if (parentNode instanceof ExprPath)
|
|
1969
|
+
// parentNode.verify();
|
|
1970
|
+
//#ENDIF
|
|
2416
1971
|
}
|
|
2417
|
-
len--;
|
|
2418
1972
|
}
|
|
2419
1973
|
}
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
function getProxy(obj, ph, path, path2) {
|
|
2423
|
-
if (!obj || !typeof obj !== 'object')
|
|
2424
|
-
return obj;
|
|
2425
|
-
|
|
2426
|
-
if (obj.$proxyHandler) {
|
|
2427
|
-
//#IFDEV
|
|
2428
|
-
if (path2)
|
|
2429
|
-
path = [...path, path2+''];
|
|
2430
|
-
//#ENDIF
|
|
2431
|
-
assert(obj.$proxyHandler.root === ph.root && JSON.stringify(obj.$proxyHandler.path) === JSON.stringify(path));
|
|
2432
|
-
return obj;
|
|
2433
|
-
}
|
|
2434
|
-
if (path2)
|
|
2435
|
-
path = [...path, path2+''];
|
|
2436
|
-
return new Proxy(obj, new ProxyHandler(ph.root, path, ph.props));
|
|
2437
|
-
}
|
|
2438
|
-
|
|
2439
|
-
/**
|
|
2440
|
-
* Call a function and record which watched variables it accesess, storing their paths in pathToTemplates.
|
|
2441
|
-
* Used by NodeGroup.applyOneExpr().
|
|
2442
|
-
* TODO: only allow this to be called once per callback.
|
|
2443
|
-
* @param callback {function}
|
|
2444
|
-
* @param ngm {NodeGroupManager}
|
|
2445
|
-
* @returns {Template} */
|
|
2446
|
-
function watchFunction(callback, ngm) {
|
|
2447
|
-
ngm.clearSubscribersIfNeeded();
|
|
2448
|
-
|
|
2449
|
-
logGets = true;
|
|
2450
|
-
|
|
2451
|
-
let transformer = () => new Template(['', ''], [callback()]);
|
|
2452
|
-
let template = transformer();
|
|
2453
|
-
for (let path of gets) {
|
|
2454
|
-
let subscriber = new Subscriber(transformer, template);
|
|
2455
|
-
ngm.subscribers.add(serializePath(path), subscriber);
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
gets = [];
|
|
2459
|
-
logGets = false;
|
|
2460
|
-
return template;
|
|
2461
|
-
}
|
|
2462
|
-
|
|
2463
|
-
/**
|
|
2464
|
-
* Represents a place where nodes will be updated.
|
|
2465
|
-
* TODO: Merge this with Template, or ExprPath? */
|
|
2466
|
-
class Subscriber {
|
|
2467
|
-
|
|
2468
|
-
/** @type {Subscriber} Used only for children of a loop. */
|
|
2469
|
-
parent;
|
|
2470
|
-
|
|
2471
|
-
/** @type {Subscriber[]} TemplateInfo for each child of a loop. */
|
|
2472
|
-
children;
|
|
2473
|
-
|
|
2474
|
-
/**
|
|
2475
|
-
* @param transformer {function} Function that turns the object at the path into a template.
|
|
2476
|
-
* @param template {Template}
|
|
2477
|
-
* @param itemTransformer {function} If a loop, this transforms each item in the loop. */
|
|
2478
|
-
constructor(transformer, template, itemTransformer=null) {
|
|
2479
|
-
this.transformer = transformer;
|
|
2480
|
-
this.template = template;
|
|
2481
|
-
|
|
2482
|
-
// Used only for loops
|
|
2483
|
-
this.itemTransformer = itemTransformer;
|
|
2484
|
-
}
|
|
2485
|
-
}
|
|
1974
|
+
return b;
|
|
1975
|
+
};
|
|
2486
1976
|
|
|
2487
1977
|
/** @typedef {boolean|string|number|function|Object|Array|Date|Node} Expr */
|
|
2488
1978
|
|
|
@@ -2513,10 +2003,10 @@ class NodeGroup {
|
|
|
2513
2003
|
/** @type {ExprPath[]} */
|
|
2514
2004
|
paths = [];
|
|
2515
2005
|
|
|
2516
|
-
/** @type {string} */
|
|
2006
|
+
/** @type {string} Key that matches the template and the expressions. */
|
|
2517
2007
|
exactKey;
|
|
2518
2008
|
|
|
2519
|
-
/** @type {string} */
|
|
2009
|
+
/** @type {string} Key that only matches the template. */
|
|
2520
2010
|
closeKey;
|
|
2521
2011
|
|
|
2522
2012
|
/** @type {boolean} Used by NodeGroupManager. */
|
|
@@ -2549,8 +2039,10 @@ class NodeGroup {
|
|
|
2549
2039
|
* @returns {NodeGroup} */
|
|
2550
2040
|
constructor(template, manager=null) {
|
|
2551
2041
|
|
|
2552
|
-
|
|
2042
|
+
/** @type {Template} */
|
|
2553
2043
|
this.template = template;
|
|
2044
|
+
|
|
2045
|
+
/** @type {NodeGroupManager} */
|
|
2554
2046
|
this.manager = manager;
|
|
2555
2047
|
|
|
2556
2048
|
// new!
|
|
@@ -2566,7 +2058,7 @@ class NodeGroup {
|
|
|
2566
2058
|
let replaceMode = typeof template.replaceMode === 'boolean'
|
|
2567
2059
|
? template.replaceMode
|
|
2568
2060
|
: fragment.children.length===1 &&
|
|
2569
|
-
fragment.firstElementChild?.tagName.replace(/-
|
|
2061
|
+
fragment.firstElementChild?.tagName.replace(/-SOLARITE-PLACEHOLDER$/, '')
|
|
2570
2062
|
=== manager?.rootEl?.tagName;
|
|
2571
2063
|
if (replaceMode) {
|
|
2572
2064
|
this.pseudoRoot = fragment.firstElementChild;
|
|
@@ -2593,7 +2085,7 @@ class NodeGroup {
|
|
|
2593
2085
|
|
|
2594
2086
|
|
|
2595
2087
|
// Update web component placeholders.
|
|
2596
|
-
// Ctrl+F "
|
|
2088
|
+
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
2597
2089
|
// Is this list needed at all?
|
|
2598
2090
|
//for (let component of shell.components)
|
|
2599
2091
|
// this.components.push(resolveNodePath(this.startNode.parentNode, getNodePath(component)))
|
|
@@ -2626,13 +2118,19 @@ class NodeGroup {
|
|
|
2626
2118
|
this.createNewComponent(el);
|
|
2627
2119
|
}
|
|
2628
2120
|
|
|
2629
|
-
if (this.manager
|
|
2121
|
+
if (this.manager?.rootEl) {
|
|
2630
2122
|
|
|
2631
2123
|
// ids
|
|
2632
2124
|
if (this.manager.options.ids !== false)
|
|
2633
2125
|
for (let path of shell.ids) {
|
|
2634
2126
|
let el = resolveNodePath(root, path);
|
|
2635
2127
|
let id = el.getAttribute('data-id') || el.getAttribute('id');
|
|
2128
|
+
|
|
2129
|
+
// Don't allow overwriting existing class properties if they already have a non-Node value.
|
|
2130
|
+
if (this.manager.rootEl[id] && !(this.manager.rootEl[id] instanceof Node))
|
|
2131
|
+
throw new Error(`${this.manager.rootEl.constructor.name}.${id} already has a value. `+
|
|
2132
|
+
`Can't set it as a reference to <${el.tagName.toLowerCase()} id="${id}">`);
|
|
2133
|
+
|
|
2636
2134
|
this.manager.rootEl[id] = el;
|
|
2637
2135
|
}
|
|
2638
2136
|
|
|
@@ -2657,20 +2155,13 @@ class NodeGroup {
|
|
|
2657
2155
|
}
|
|
2658
2156
|
}
|
|
2659
2157
|
|
|
2660
|
-
updateStyles() {
|
|
2661
|
-
if (this.styles)
|
|
2662
|
-
for (let [style, oldText] of this.styles) {
|
|
2663
|
-
let newText = style.textContent;
|
|
2664
|
-
if (oldText !== newText)
|
|
2665
|
-
Util.bindStyles(style, this.manager.rootEl);
|
|
2666
|
-
}
|
|
2667
|
-
}
|
|
2668
|
-
|
|
2669
2158
|
/**
|
|
2670
2159
|
* Use the paths to insert the given expressions.
|
|
2671
2160
|
* Dispatches expression handling to other functions depending on the path type.
|
|
2672
|
-
* @param exprs {(*|*[]|function|Template)[]}
|
|
2673
|
-
|
|
2161
|
+
* @param exprs {(*|*[]|function|Template)[]}
|
|
2162
|
+
* @param paths {?ExprPath[]} Optional. */
|
|
2163
|
+
applyExprs(exprs, paths=null) {
|
|
2164
|
+
paths = paths || this.paths;
|
|
2674
2165
|
|
|
2675
2166
|
/*#IFDEV*/this.verify();/*#ENDIF*/
|
|
2676
2167
|
// Update exprs at paths.
|
|
@@ -2678,7 +2169,7 @@ class NodeGroup {
|
|
|
2678
2169
|
|
|
2679
2170
|
// We apply them in reverse order so that a <select> box has its options created from an expression
|
|
2680
2171
|
// before its value attribute is set via an expression.
|
|
2681
|
-
for (let path of
|
|
2172
|
+
for (let path of paths.toReversed()) {
|
|
2682
2173
|
expr = exprs[exprIndex];
|
|
2683
2174
|
|
|
2684
2175
|
// Nodes
|
|
@@ -2689,8 +2180,8 @@ class NodeGroup {
|
|
|
2689
2180
|
|
|
2690
2181
|
// Attributes
|
|
2691
2182
|
else {
|
|
2692
|
-
let node = path.nodeMarker;
|
|
2693
|
-
let
|
|
2183
|
+
let node = path.nodeMarker;
|
|
2184
|
+
let el = (this.manager?.rootEl && node === this.pseudoRoot) ? this.manager.rootEl : node;
|
|
2694
2185
|
/*#IFDEV*/assert(node);/*#ENDIF*/
|
|
2695
2186
|
|
|
2696
2187
|
// This is necessary both here and below.
|
|
@@ -2700,10 +2191,10 @@ class NodeGroup {
|
|
|
2700
2191
|
}
|
|
2701
2192
|
|
|
2702
2193
|
if (path.type === PathType.Multiple)
|
|
2703
|
-
path.applyMultipleAttribs(
|
|
2194
|
+
path.applyMultipleAttribs(el, expr);
|
|
2704
2195
|
|
|
2705
2196
|
// Capture attribute expressions to later send to the constructor of a web component.
|
|
2706
|
-
// Ctrl+F "
|
|
2197
|
+
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
2707
2198
|
else if (path.nodeMarker !== this.pseudoRoot && path.type === PathType.Component)
|
|
2708
2199
|
this.currentComponentProps[path.attrName] = expr;
|
|
2709
2200
|
|
|
@@ -2712,13 +2203,13 @@ class NodeGroup {
|
|
|
2712
2203
|
|
|
2713
2204
|
// Event attribute value
|
|
2714
2205
|
if (path.attrValue===null && (typeof expr === 'function' || Array.isArray(expr)) && isEvent(path.attrName)) {
|
|
2715
|
-
let root = this.manager
|
|
2716
|
-
path.applyEventAttrib(
|
|
2206
|
+
let root = this.manager?.rootEl || this.startNode.parentNode; // latter is used when constructing a whole element.
|
|
2207
|
+
path.applyEventAttrib(el, expr, root);
|
|
2717
2208
|
}
|
|
2718
2209
|
|
|
2719
2210
|
// Regular attribute value.
|
|
2720
|
-
else
|
|
2721
|
-
exprIndex = path.applyValueAttrib(
|
|
2211
|
+
else // One node value may have multiple expressions. Here we apply them all at once.
|
|
2212
|
+
exprIndex = path.applyValueAttrib(el, exprs, exprIndex);
|
|
2722
2213
|
}
|
|
2723
2214
|
|
|
2724
2215
|
lastNode = path.nodeMarker;
|
|
@@ -2747,6 +2238,10 @@ class NodeGroup {
|
|
|
2747
2238
|
/*#IFDEV*/this.verify();/*#ENDIF*/
|
|
2748
2239
|
}
|
|
2749
2240
|
|
|
2241
|
+
applyExpr(path, expr) {
|
|
2242
|
+
// TODO: Use this if I can figure out how to adapt applyValueAttrib() to it.
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2750
2245
|
/**
|
|
2751
2246
|
* Insert/replace the nodes created by a single expression.
|
|
2752
2247
|
* Called by applyExprs()
|
|
@@ -2768,7 +2263,7 @@ class NodeGroup {
|
|
|
2768
2263
|
//for (let ng of path.nodeGroups) // TODO: Is this necessary?
|
|
2769
2264
|
// ng.parentPath = null;
|
|
2770
2265
|
path.nodeGroups = [];
|
|
2771
|
-
|
|
2266
|
+
path.apply(expr, newNodes, secondPass);
|
|
2772
2267
|
this.existingTextNodes = null;
|
|
2773
2268
|
|
|
2774
2269
|
// TODO: Create an array of old vs Nodes and NodeGroups together.
|
|
@@ -2858,117 +2353,32 @@ class NodeGroup {
|
|
|
2858
2353
|
}
|
|
2859
2354
|
}
|
|
2860
2355
|
|
|
2861
|
-
// TODO: Move to ExprPath?
|
|
2862
|
-
applyOneExpr(expr, path, newNodes, secondPass) {
|
|
2863
|
-
|
|
2864
|
-
if (expr instanceof Template) {
|
|
2865
|
-
expr.parentPath = path;
|
|
2866
|
-
expr.nodegroup = this;
|
|
2867
|
-
|
|
2868
|
-
//if (window.debug && expr.exprs[0] === 'Banana' && path.nodeGroups.length === 0)
|
|
2869
|
-
//if (window.debug && expr.exprs[0] === 'Banana')
|
|
2870
|
-
// debugger;
|
|
2871
|
-
|
|
2872
|
-
let ng = this.manager.getNodeGroup(expr, true);
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
if (ng) {
|
|
2876
|
-
//#IFDEV
|
|
2877
|
-
// Make sure the nodeCache of the ExprPath we took it from is sitll valid.
|
|
2878
|
-
if (ng.parentPath)
|
|
2879
|
-
ng.parentPath.verify();
|
|
2880
|
-
//#ENDIF
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
// TODO: Track ranges of changed nodes and only pass those to udomdiff?
|
|
2884
|
-
// But will that break the swap benchmark?
|
|
2885
|
-
newNodes.push(...ng.getNodes());
|
|
2886
|
-
path.nodeGroups.push(ng);
|
|
2887
|
-
}
|
|
2888
|
-
|
|
2889
|
-
// If expression, evaluate later to find partial match.
|
|
2890
|
-
else {
|
|
2891
|
-
secondPass.push([newNodes.length, path.nodeGroups.length]);
|
|
2892
|
-
newNodes.push(expr);
|
|
2893
|
-
path.nodeGroups.push(null); // placeholder
|
|
2894
|
-
}
|
|
2895
|
-
}
|
|
2896
|
-
|
|
2897
|
-
// Node created by an expression.
|
|
2898
|
-
else if (expr instanceof Node) {
|
|
2899
|
-
|
|
2900
|
-
// DocumentFragment created by an expression.
|
|
2901
|
-
if (expr instanceof DocumentFragment)
|
|
2902
|
-
newNodes.push(...expr.childNodes);
|
|
2903
|
-
else
|
|
2904
|
-
newNodes.push(expr);
|
|
2905
|
-
}
|
|
2906
|
-
|
|
2907
|
-
else if (Array.isArray(expr))
|
|
2908
|
-
for (let subExpr of expr)
|
|
2909
|
-
this.applyOneExpr(subExpr, path, newNodes, secondPass);
|
|
2910
|
-
|
|
2911
|
-
else if (typeof expr === 'function') {
|
|
2912
|
-
expr = watchFunction(expr, this.manager);
|
|
2913
|
-
|
|
2914
|
-
this.applyOneExpr(expr, path, newNodes, secondPass);
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
// Text
|
|
2918
|
-
else {
|
|
2919
|
-
// Convert falsy values (but not 0) to empty string.
|
|
2920
|
-
// Convert numbers to string so they compare the same.
|
|
2921
|
-
let text = (expr === undefined || expr === false || expr === null) ? '' : expr + '';
|
|
2922
|
-
|
|
2923
|
-
// Fast path for updating the text of a single text node.
|
|
2924
|
-
let first = path.nodeBefore.nextSibling;
|
|
2925
|
-
if (first.nodeType === 3 && first.nextSibling === path.nodeMarker && !newNodes.includes(first)) {
|
|
2926
|
-
if (first.textContent !== text)
|
|
2927
|
-
first.textContent = text;
|
|
2928
|
-
|
|
2929
|
-
newNodes.push(first);
|
|
2930
|
-
}
|
|
2931
|
-
|
|
2932
|
-
else {
|
|
2933
|
-
// TODO: Optimize this into a Set or Map or something?
|
|
2934
|
-
if (!this.existingTextNodes)
|
|
2935
|
-
this.existingTextNodes = path.getNodes().filter(n => n.nodeType === 3);
|
|
2936
|
-
|
|
2937
|
-
let idx = this.existingTextNodes.findIndex(n => n.textContent === text);
|
|
2938
|
-
if (idx !== -1)
|
|
2939
|
-
newNodes.push(...this.existingTextNodes.splice(idx, 1));
|
|
2940
|
-
else
|
|
2941
|
-
newNodes.push(path.parentNode.ownerDocument.createTextNode(text));
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
}
|
|
2945
|
-
|
|
2946
2356
|
/**
|
|
2947
2357
|
* Create a nested RedComponent or call render with the new props.
|
|
2948
2358
|
* @param el {Solarite:HTMLElement}
|
|
2949
2359
|
* @param props {Object} */
|
|
2950
2360
|
applyComponentExprs(el, props) {
|
|
2951
|
-
|
|
2361
|
+
|
|
2952
2362
|
// TODO: Does a hash of this already exist somewhere?
|
|
2953
2363
|
// Perhaps if Components were treated as child NodeGroups, which would need to be the child of an ExprPath,
|
|
2954
2364
|
// then we could re-use the hash and logic from NodeManager?
|
|
2955
2365
|
let newHash = getObjectHash(props);
|
|
2956
|
-
|
|
2957
|
-
let isPreHtmlElement = el.tagName.endsWith('-
|
|
2366
|
+
|
|
2367
|
+
let isPreHtmlElement = el.tagName.endsWith('-SOLARITE-PLACEHOLDER');
|
|
2958
2368
|
let isPreIsElement = el.hasAttribute('_is');
|
|
2959
|
-
|
|
2960
|
-
|
|
2369
|
+
|
|
2370
|
+
|
|
2961
2371
|
// Instantiate a placeholder.
|
|
2962
2372
|
if (isPreHtmlElement || isPreIsElement)
|
|
2963
2373
|
el = this.createNewComponent(el, isPreHtmlElement, props);
|
|
2964
|
-
|
|
2374
|
+
|
|
2965
2375
|
// Update params of placeholder.
|
|
2966
2376
|
else if (el.render) {
|
|
2967
2377
|
let oldHash = componentHash.get(el);
|
|
2968
2378
|
if (oldHash !== newHash)
|
|
2969
2379
|
el.render(props); // Pass new values of props to render so it can decide how it wants to respond.
|
|
2970
2380
|
}
|
|
2971
|
-
|
|
2381
|
+
|
|
2972
2382
|
componentHash.set(el, newHash);
|
|
2973
2383
|
}
|
|
2974
2384
|
|
|
@@ -2987,8 +2397,8 @@ class NodeGroup {
|
|
|
2987
2397
|
isPreHtmlElement = !el.hasAttribute('_is');
|
|
2988
2398
|
|
|
2989
2399
|
let tagName = (isPreHtmlElement
|
|
2990
|
-
? el.tagName.endsWith('-
|
|
2991
|
-
? el.tagName.slice(0, -
|
|
2400
|
+
? el.tagName.endsWith('-SOLARITE-PLACEHOLDER')
|
|
2401
|
+
? el.tagName.slice(0, -21)
|
|
2992
2402
|
: el.tagName
|
|
2993
2403
|
: el.getAttribute('is')).toLowerCase();
|
|
2994
2404
|
|
|
@@ -3108,6 +2518,17 @@ class NodeGroup {
|
|
|
3108
2518
|
}
|
|
3109
2519
|
|
|
3110
2520
|
|
|
2521
|
+
|
|
2522
|
+
updateStyles() {
|
|
2523
|
+
if (this.styles)
|
|
2524
|
+
for (let [style, oldText] of this.styles) {
|
|
2525
|
+
let newText = style.textContent;
|
|
2526
|
+
if (oldText !== newText)
|
|
2527
|
+
Util.bindStyles(style, this.manager.rootEl);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
|
|
3111
2532
|
//#IFDEV
|
|
3112
2533
|
/**
|
|
3113
2534
|
* @deprecated
|
|
@@ -3179,12 +2600,21 @@ class NodeGroup {
|
|
|
3179
2600
|
|
|
3180
2601
|
let componentHash = new WeakMap();
|
|
3181
2602
|
|
|
2603
|
+
/**
|
|
2604
|
+
* Tools for watch variables and performing precise renders.
|
|
2605
|
+
*/
|
|
2606
|
+
|
|
2607
|
+
function serializePath(path) {
|
|
2608
|
+
// Convert any array indices to strings, so serialized comparisons work.
|
|
2609
|
+
return JSON.stringify([getObjectId(path[0]), ...path.slice(1).map(item => item+'')])
|
|
2610
|
+
|
|
2611
|
+
}
|
|
2612
|
+
|
|
3182
2613
|
/**
|
|
3183
2614
|
* @typedef {Object} RenderOptions
|
|
3184
2615
|
* @property {boolean=} styles - Indicates whether the Courage component is present.
|
|
3185
2616
|
* @property {boolean=} scripts - Indicates whether the Power component is present.
|
|
3186
|
-
* @property {boolean=} ids
|
|
3187
|
-
*
|
|
2617
|
+
* @property {boolean=} ids *
|
|
3188
2618
|
* @property {?boolean} render
|
|
3189
2619
|
* Used only when options are given to a class super constructor inheriting from Solarite.
|
|
3190
2620
|
* True to call render() immediately in super constructor.
|
|
@@ -3239,6 +2669,7 @@ class NodeGroupManager {
|
|
|
3239
2669
|
* @param rootEl {HTMLElement|DocumentFragment} If not specified, the first element of the html will be the rootEl. */
|
|
3240
2670
|
constructor(rootEl=null) {
|
|
3241
2671
|
this.rootEl = rootEl;
|
|
2672
|
+
|
|
3242
2673
|
/*
|
|
3243
2674
|
//#IFDEV
|
|
3244
2675
|
|
|
@@ -3280,126 +2711,6 @@ class NodeGroupManager {
|
|
|
3280
2711
|
*/
|
|
3281
2712
|
}
|
|
3282
2713
|
|
|
3283
|
-
/**
|
|
3284
|
-
* Render the main template, which may indirectly call renderTemplate() to create children.
|
|
3285
|
-
* @param template {Template}
|
|
3286
|
-
* @param options {RenderOptions}
|
|
3287
|
-
* @return {?DocumentFragment} */
|
|
3288
|
-
render(template, options={}) {
|
|
3289
|
-
this.mutationWatcherEnabled = false;
|
|
3290
|
-
this.options = options;
|
|
3291
|
-
this.clearSubscribers = false;
|
|
3292
|
-
|
|
3293
|
-
//#IFDEV
|
|
3294
|
-
this.modifications = {
|
|
3295
|
-
created: [],
|
|
3296
|
-
updated: [],
|
|
3297
|
-
moved: [],
|
|
3298
|
-
deleted: []
|
|
3299
|
-
};
|
|
3300
|
-
//#ENDIF
|
|
3301
|
-
|
|
3302
|
-
if (!template && template !== '') {
|
|
3303
|
-
this.rootEl.outerHTML = '';
|
|
3304
|
-
this.mutationWatcherEnabled = true;
|
|
3305
|
-
return null;
|
|
3306
|
-
}
|
|
3307
|
-
|
|
3308
|
-
// Fast path for empty component.
|
|
3309
|
-
if (template.html?.length === 1 && !template.html[0]) {
|
|
3310
|
-
this.rootEl.innerHTML = '';
|
|
3311
|
-
}
|
|
3312
|
-
else {
|
|
3313
|
-
|
|
3314
|
-
// Find or create a NodeGroup for the template.
|
|
3315
|
-
// This updates all nodes from the template.
|
|
3316
|
-
let close;
|
|
3317
|
-
let exact = this.getNodeGroup(template, true);
|
|
3318
|
-
if (!exact) {
|
|
3319
|
-
close = this.getNodeGroup(template, false);
|
|
3320
|
-
}
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
let firstTime = !this.rootNg;
|
|
3324
|
-
this.rootNg = exact || close;
|
|
3325
|
-
|
|
3326
|
-
// Reparent NodeGroup
|
|
3327
|
-
// TODO: Move this to NodeGroup?
|
|
3328
|
-
let parent = this.rootNg.getParentNode();
|
|
3329
|
-
if (!this.rootEl)
|
|
3330
|
-
this.rootEl = parent;
|
|
3331
|
-
|
|
3332
|
-
// If this is the first time rendering this element.
|
|
3333
|
-
else if (firstTime) {
|
|
3334
|
-
|
|
3335
|
-
// Save slot children
|
|
3336
|
-
let fragment;
|
|
3337
|
-
if (this.rootEl.childNodes.length) {
|
|
3338
|
-
fragment = document.createDocumentFragment();
|
|
3339
|
-
fragment.append(...this.rootEl.childNodes);
|
|
3340
|
-
}
|
|
3341
|
-
|
|
3342
|
-
// Add rendered elements.
|
|
3343
|
-
if (parent instanceof DocumentFragment)
|
|
3344
|
-
this.rootEl.append(parent);
|
|
3345
|
-
else if (parent)
|
|
3346
|
-
this.rootEl.append(...parent.childNodes);
|
|
3347
|
-
|
|
3348
|
-
// Apply slot children
|
|
3349
|
-
if (fragment) {
|
|
3350
|
-
for (let slot of this.rootEl.querySelectorAll('slot[name]')) {
|
|
3351
|
-
let name = slot.getAttribute('name');
|
|
3352
|
-
if (name)
|
|
3353
|
-
slot.append(...fragment.querySelectorAll(`[slot='${name}']`));
|
|
3354
|
-
}
|
|
3355
|
-
let unamedSlot = this.rootEl.querySelector('slot:not([name])');
|
|
3356
|
-
if (unamedSlot)
|
|
3357
|
-
unamedSlot.append(fragment);
|
|
3358
|
-
}
|
|
3359
|
-
|
|
3360
|
-
}
|
|
3361
|
-
|
|
3362
|
-
// this.rootNg was rendered as childrenOnly=true
|
|
3363
|
-
// Apply attributes from a root element to the real root element.
|
|
3364
|
-
let ng = this.rootNg;
|
|
3365
|
-
if (ng.pseudoRoot && ng.pseudoRoot !== this.rootEl) {
|
|
3366
|
-
/*#IFDEV*/assert(this.rootEl);/*#ENDIF*/
|
|
3367
|
-
|
|
3368
|
-
// Remove old attributes
|
|
3369
|
-
// for (let attrib of this.rootEl.attributes)
|
|
3370
|
-
// if (attrib.name !== 'is' && attrib.name !== 'data-style' && !ng.pseudoRoot.hasAttribute(attrib.name))
|
|
3371
|
-
// this.rootEl.removeAttribute(attrib.name)
|
|
3372
|
-
|
|
3373
|
-
// Add/set new attributes
|
|
3374
|
-
if (firstTime)
|
|
3375
|
-
for (let attrib of ng.pseudoRoot.attributes)
|
|
3376
|
-
if (!this.rootEl.hasAttribute(attrib.name))
|
|
3377
|
-
this.rootEl.setAttribute(attrib.name, attrib.value);
|
|
3378
|
-
|
|
3379
|
-
// ng.startNode = ng.endNode = this.rootEl;
|
|
3380
|
-
// ng.nodesCache = [ng.startNode]
|
|
3381
|
-
// for (let path of ng.paths) {
|
|
3382
|
-
// if (path.nodeMarker === ng.rootEl)
|
|
3383
|
-
// path.nodeMarker = this.rootEl;
|
|
3384
|
-
// path.nodesCache = null;
|
|
3385
|
-
// /*#IFDEV*/assert(path.nodeBefore !== ng.rootEl)/*#ENDIF*/
|
|
3386
|
-
// }
|
|
3387
|
-
//
|
|
3388
|
-
// ng.rootEl = this.rootEl;
|
|
3389
|
-
}
|
|
3390
|
-
|
|
3391
|
-
/*#IFDEV*/this.rootNg.verify();/*#ENDIF*/
|
|
3392
|
-
this.reset();
|
|
3393
|
-
/*#IFDEV*/this.rootNg.verify();/*#ENDIF*/
|
|
3394
|
-
}
|
|
3395
|
-
|
|
3396
|
-
this.mutationWatcherEnabled = true;
|
|
3397
|
-
return this.rootEl;
|
|
3398
|
-
//#IFDEV
|
|
3399
|
-
//return this.modifications;
|
|
3400
|
-
//#ENDIF
|
|
3401
|
-
}
|
|
3402
|
-
|
|
3403
2714
|
|
|
3404
2715
|
/**
|
|
3405
2716
|
*
|
|
@@ -3498,6 +2809,8 @@ class NodeGroupManager {
|
|
|
3498
2809
|
// But it can still be a close match, so we don't use this code.
|
|
3499
2810
|
success = this.nodeGroupsAvailable.delete(ng2.closeKey, ng2);
|
|
3500
2811
|
/*#IFDEV*/assert(success);/*#ENDIF*/
|
|
2812
|
+
|
|
2813
|
+
this.nodeGroupsInUse.push(ng2);
|
|
3501
2814
|
}
|
|
3502
2815
|
}
|
|
3503
2816
|
}
|
|
@@ -3528,7 +2841,7 @@ class NodeGroupManager {
|
|
|
3528
2841
|
* but don't reparent it if it's somewhere else.
|
|
3529
2842
|
* @param template {Template}
|
|
3530
2843
|
* @param exact {?boolean}
|
|
3531
|
-
* @param createForWatch
|
|
2844
|
+
* @param createForWatch Deprecated.
|
|
3532
2845
|
* @return {?NodeGroup} */
|
|
3533
2846
|
getNodeGroup(template, exact=null, createForWatch=false) {
|
|
3534
2847
|
|
|
@@ -3582,7 +2895,7 @@ class NodeGroupManager {
|
|
|
3582
2895
|
// Perhaps also result could cache its last exprKey and then we'd use only one map?
|
|
3583
2896
|
ng.exactKey = exactKey;
|
|
3584
2897
|
ng.closeKey = closeKey;
|
|
3585
|
-
if (createForWatch)
|
|
2898
|
+
if (createForWatch)
|
|
3586
2899
|
this.nodeGroupsAvailable.add(ng.exactKey, ng);
|
|
3587
2900
|
else
|
|
3588
2901
|
this.nodeGroupsInUse.push(ng);
|
|
@@ -3625,7 +2938,10 @@ class NodeGroupManager {
|
|
|
3625
2938
|
//pathToLoopInfo = new MultiValueMap(); // uses a Set() for each value.
|
|
3626
2939
|
clearSubscribers = false;
|
|
3627
2940
|
|
|
2941
|
+
//#IFDEV
|
|
2942
|
+
|
|
3628
2943
|
/**
|
|
2944
|
+
* @deprecated - part of watch.js (Watch v1)
|
|
3629
2945
|
* One path may be used to loop in more than one place, so we use this to get every anchor from each loop.
|
|
3630
2946
|
* @param path {Array}
|
|
3631
2947
|
* @return {LoopInfo[]} A function that gets the loop anchor NodeGroup */
|
|
@@ -3634,6 +2950,8 @@ class NodeGroupManager {
|
|
|
3634
2950
|
return [...this.pathToLoopInfo.getAll(serializedArrayPath)]; // This is set inside forEach()
|
|
3635
2951
|
}
|
|
3636
2952
|
|
|
2953
|
+
//#ENDIF
|
|
2954
|
+
|
|
3637
2955
|
|
|
3638
2956
|
/**
|
|
3639
2957
|
* @deprecated
|
|
@@ -3660,7 +2978,10 @@ class NodeGroupManager {
|
|
|
3660
2978
|
* Get the NodeGroupManager for a Web Component.
|
|
3661
2979
|
* @param rootEl {Solarite|HTMLElement}
|
|
3662
2980
|
* @return {NodeGroupManager} */
|
|
3663
|
-
static get(rootEl) {
|
|
2981
|
+
static get(rootEl=null) {
|
|
2982
|
+
if (!rootEl)
|
|
2983
|
+
return new NodeGroupManager();
|
|
2984
|
+
|
|
3664
2985
|
let ngm = nodeGroupManagers.get(rootEl);
|
|
3665
2986
|
if (!ngm) {
|
|
3666
2987
|
ngm = new NodeGroupManager(rootEl);
|
|
@@ -3723,16 +3044,7 @@ NodeGroupManager.pendingChildren = [];
|
|
|
3723
3044
|
/**
|
|
3724
3045
|
* Each Element that has Expr children has an associated NodeGroupManager here.
|
|
3725
3046
|
* @type {WeakMap<HTMLElement, NodeGroupManager>} */
|
|
3726
|
-
let nodeGroupManagers = new WeakMap();
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
class LoopInfo {
|
|
3731
|
-
constructor(loopTemplate, itemTransformer) {
|
|
3732
|
-
this.template = loopTemplate;
|
|
3733
|
-
this.itemTransformer = itemTransformer;
|
|
3734
|
-
}
|
|
3735
|
-
}
|
|
3047
|
+
let nodeGroupManagers = new WeakMap();
|
|
3736
3048
|
|
|
3737
3049
|
/**
|
|
3738
3050
|
* Convert strings to HTMLNodes.
|
|
@@ -3757,10 +3069,10 @@ class LoopInfo {
|
|
|
3757
3069
|
* 6. r('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
|
|
3758
3070
|
* 7. r()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which includes properly handling nested components and r`` sub-expressions.
|
|
3759
3071
|
* 8. r(template) // Render Template created by #1.
|
|
3760
|
-
* 9. r(() => r`<b>Hello</b
|
|
3072
|
+
* 9. r(() => r`<b>Hello</b>`, {...}); // Create dynamic element that has a render() function.
|
|
3761
3073
|
*
|
|
3762
3074
|
* @param htmlStrings {?HTMLElement|string|string[]|function():Template}
|
|
3763
|
-
* @param exprs {*[]|string|Template}
|
|
3075
|
+
* @param exprs {*[]|string|Template|Object}
|
|
3764
3076
|
* @return {Node|HTMLElement|Template} */
|
|
3765
3077
|
function r(htmlStrings=undefined, ...exprs) {
|
|
3766
3078
|
|
|
@@ -3774,9 +3086,8 @@ function r(htmlStrings=undefined, ...exprs) {
|
|
|
3774
3086
|
|
|
3775
3087
|
// 2. Render template created by #4 to element.
|
|
3776
3088
|
if (exprs[0] instanceof Template) {
|
|
3777
|
-
let ngm = NodeGroupManager.get(parent);
|
|
3778
3089
|
let options = exprs[1];
|
|
3779
|
-
|
|
3090
|
+
template.render(parent, options);
|
|
3780
3091
|
|
|
3781
3092
|
// Append on the first go.
|
|
3782
3093
|
if (!parent.childNodes.length && this) {
|
|
@@ -3795,15 +3106,16 @@ function r(htmlStrings=undefined, ...exprs) {
|
|
|
3795
3106
|
return (htmlStrings, ...exprs) => {
|
|
3796
3107
|
rendered.add(parent);
|
|
3797
3108
|
let template = r(htmlStrings, ...exprs);
|
|
3798
|
-
|
|
3799
|
-
return ngm.render(template, options);
|
|
3109
|
+
return template.render(parent, options);
|
|
3800
3110
|
}
|
|
3801
3111
|
}
|
|
3802
3112
|
|
|
3803
3113
|
// null for expr[0], remove whole element.
|
|
3114
|
+
// This path never happens?
|
|
3804
3115
|
else {
|
|
3805
|
-
|
|
3806
|
-
ngm.
|
|
3116
|
+
throw new Error('unsupported');
|
|
3117
|
+
//let ngm = NodeGroupManager.get(parent);
|
|
3118
|
+
//ngm.render(null, exprs[1])
|
|
3807
3119
|
}
|
|
3808
3120
|
}
|
|
3809
3121
|
|
|
@@ -3831,16 +3143,16 @@ function r(htmlStrings=undefined, ...exprs) {
|
|
|
3831
3143
|
return (htmlStrings, ...exprs) => {
|
|
3832
3144
|
//rendered.add(parent)
|
|
3833
3145
|
let template = r(htmlStrings, ...exprs);
|
|
3834
|
-
return template.
|
|
3146
|
+
return template.render();
|
|
3835
3147
|
}
|
|
3836
3148
|
}
|
|
3837
3149
|
|
|
3838
3150
|
// 8.
|
|
3839
3151
|
else if (htmlStrings instanceof Template) {
|
|
3840
|
-
|
|
3841
|
-
return ngm.render(htmlStrings);
|
|
3152
|
+
return htmlStrings.render();
|
|
3842
3153
|
}
|
|
3843
3154
|
|
|
3155
|
+
|
|
3844
3156
|
// 9. Create dynamic element with render() function.
|
|
3845
3157
|
else if (typeof htmlStrings === 'function') {
|
|
3846
3158
|
let getTemplate = htmlStrings;
|
|
@@ -3849,17 +3161,27 @@ function r(htmlStrings=undefined, ...exprs) {
|
|
|
3849
3161
|
if (typeof template === 'string')
|
|
3850
3162
|
throw new Error(`Please add the "r" prefix before the string "${template}"`)
|
|
3851
3163
|
|
|
3852
|
-
let ngm = new NodeGroupManager();
|
|
3853
3164
|
template.replaceMode = true;
|
|
3854
|
-
let el =
|
|
3165
|
+
let el = template.render();
|
|
3855
3166
|
|
|
3167
|
+
// Create the render() function from the function we were given.
|
|
3856
3168
|
el.render = (function() {
|
|
3857
3169
|
template = getTemplate();
|
|
3858
|
-
|
|
3170
|
+
template.render(el);
|
|
3859
3171
|
}).bind(el);
|
|
3860
3172
|
|
|
3173
|
+
|
|
3174
|
+
// The second argument was an object of additional properties to add.
|
|
3175
|
+
let props = exprs[0];
|
|
3176
|
+
for (let name in props)
|
|
3177
|
+
if (typeof props[name] === 'function')
|
|
3178
|
+
el[name] = props[name].bind(el);
|
|
3179
|
+
else
|
|
3180
|
+
el[name] = props[name];
|
|
3181
|
+
|
|
3861
3182
|
return el;
|
|
3862
3183
|
}
|
|
3184
|
+
|
|
3863
3185
|
else
|
|
3864
3186
|
throw new Error('Unsupported arguments.')
|
|
3865
3187
|
}
|
|
@@ -3874,6 +3196,10 @@ function r(htmlStrings=undefined, ...exprs) {
|
|
|
3874
3196
|
* @type {WeakSet<HTMLElement>} */
|
|
3875
3197
|
let rendered = new WeakSet();
|
|
3876
3198
|
|
|
3199
|
+
//import {watchGet, watchSet} from "./watch.js";
|
|
3200
|
+
|
|
3201
|
+
|
|
3202
|
+
|
|
3877
3203
|
function defineClass(Class, tagName, extendsTag) {
|
|
3878
3204
|
if (!customElements.getName(Class)) { // If not previously defined.
|
|
3879
3205
|
tagName = tagName || camelToDashes(Class.name);
|
|
@@ -3904,9 +3230,15 @@ let connected = new WeakSet();
|
|
|
3904
3230
|
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
3905
3231
|
* 3. Child elements are added before constructor is called. But they're also passed to the constructor.
|
|
3906
3232
|
* 4. We can use this.html = r`...` to set html.
|
|
3907
|
-
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
3233
|
+
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
3234
|
+
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
3908
3235
|
* 6. Can we extend from other element types like TR?
|
|
3909
3236
|
*
|
|
3237
|
+
* Advantages to inheriting from HTMLElement
|
|
3238
|
+
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
3239
|
+
* 2. We can inherit from things like HTMLTableRowElement directly.
|
|
3240
|
+
* 3. There's less magic, since everyone is familiar with defining custom elements.
|
|
3241
|
+
*
|
|
3910
3242
|
* @param extendsTag {?string}
|
|
3911
3243
|
* @return {Class} */
|
|
3912
3244
|
function createSolarite(extendsTag=null) {
|
|
@@ -3970,7 +3302,7 @@ function createSolarite(extendsTag=null) {
|
|
|
3970
3302
|
if (ch)
|
|
3971
3303
|
(this.querySelector('slot') || this).append(...ch);
|
|
3972
3304
|
|
|
3973
|
-
|
|
3305
|
+
/** @deprecated */
|
|
3974
3306
|
Object.defineProperty(this, 'html', {
|
|
3975
3307
|
set(html) {
|
|
3976
3308
|
rendered.add(this);
|
|
@@ -4020,7 +3352,9 @@ function createSolarite(extendsTag=null) {
|
|
|
4020
3352
|
defineClass(this, tagName, extendsTag);
|
|
4021
3353
|
}
|
|
4022
3354
|
|
|
3355
|
+
//#IFDEV
|
|
4023
3356
|
|
|
3357
|
+
/** @deprecated */
|
|
4024
3358
|
renderWatched() {
|
|
4025
3359
|
let ngm = NodeGroupManager.get(this);
|
|
4026
3360
|
|
|
@@ -4094,8 +3428,7 @@ function createSolarite(extendsTag=null) {
|
|
|
4094
3428
|
// Create new NodeGroup
|
|
4095
3429
|
let ng = ngm.getNodeGroup(template, false, true);
|
|
4096
3430
|
ng.parentPath = beforeNg?.parentPath || loopInfo.template.parentPath;
|
|
4097
|
-
|
|
4098
|
-
assert(ng.parentPath);/*#ENDIF*/
|
|
3431
|
+
|
|
4099
3432
|
for (let node of ng.getNodes())
|
|
4100
3433
|
beforeNode.parentNode.insertBefore(node, beforeNode);
|
|
4101
3434
|
|
|
@@ -4125,8 +3458,9 @@ function createSolarite(extendsTag=null) {
|
|
|
4125
3458
|
/**
|
|
4126
3459
|
* @deprecated Use the getArg() function instead. */
|
|
4127
3460
|
getArg(name, val=null, type=ArgType.String) {
|
|
4128
|
-
|
|
3461
|
+
throw new Error('deprecated');
|
|
4129
3462
|
}
|
|
3463
|
+
//#ENDIF
|
|
4130
3464
|
}
|
|
4131
3465
|
}
|
|
4132
3466
|
|
|
@@ -4138,6 +3472,10 @@ let Solarite = new Proxy(createSolarite(), {
|
|
|
4138
3472
|
return createSolarite(...args)
|
|
4139
3473
|
}
|
|
4140
3474
|
});
|
|
4141
|
-
|
|
3475
|
+
|
|
3476
|
+
|
|
3477
|
+
//Experimental:
|
|
3478
|
+
//export {forEach, watchGet, watchSet} from './watch.js' // old, unfinished
|
|
3479
|
+
//export {watch} from './watch2.js'; // unfinished
|
|
4142
3480
|
|
|
4143
|
-
export { ArgType, Solarite,
|
|
3481
|
+
export { ArgType, Solarite, Template, getArg, r };
|