solarite 0.1.0 → 0.2.0
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/benchmarks/naive/Solarite.min.js +4 -0
- package/benchmarks/naive/index.html +14 -0
- package/benchmarks/naive/main.js +339 -0
- package/benchmarks/naive/package-lock.json +13 -0
- package/benchmarks/naive/package.json +23 -0
- package/benchmarks/readme.md +48 -0
- package/build/build.bat +3 -2
- package/build/build.js +1 -1
- package/dist/Solarite-debug.js +1941 -2791
- package/dist/Solarite.js +1697 -2511
- package/dist/Solarite.min.js +3 -3
- package/dist/udomdiff-license.txt +18 -0
- package/docs/index.md +695 -235
- package/docs/js/Playground.js +1 -1
- package/docs/js/codemirror/codemirror6.js +3683 -3206
- package/docs/js/codemirror/themeSolarIce.js +2 -2
- package/docs/js/documentation.js +2 -2
- package/docs/js/ui/CodeEditor.js +183 -45
- package/docs/js/ui/FlexResizer.js +15 -5
- package/docs/js/util/Errors.js +11 -0
- package/docs/media/documentation.css +6 -3
- package/index.html +462 -26
- package/package.json +1 -1
- package/readme.md +11 -1
- package/src/solarite/ExprPath.js +422 -135
- package/src/solarite/Globals.js +53 -0
- package/src/solarite/NodeGroup.js +300 -388
- package/src/solarite/Shell.js +31 -33
- package/src/solarite/Solarite.js +17 -2
- package/src/solarite/Template.js +75 -25
- package/src/solarite/Util.js +131 -7
- package/src/solarite/createSolarite.js +32 -25
- package/src/solarite/getArg.js +12 -12
- package/src/solarite/hash.js +18 -35
- package/src/solarite/r.js +128 -118
- package/src/solarite/watch3.js +98 -0
- package/src/{solarite → unused}/NodeGroupManager.js +86 -224
- package/src/unused/onConnect.js +79 -0
- package/src/{solarite → unused}/watch.js +2 -2
- package/src/{solarite → unused}/watch2.js +4 -4
- package/src/{solarite → util}/MultiValueMap.js +23 -12
- package/src/util/WeakArray.js +33 -0
- package/tests/Solarite.test.js +1108 -166
- package/tests/Testimony.js +274 -67
- package/tests/index.html +6 -35
- package/tests/run.bat +2 -0
- package/tests/NodeGroup.test.js +0 -115
- package/tests/Shell.test.js +0 -75
package/src/solarite/getArg.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* There are three ways to create an instance of a Solarite Component:
|
|
6
6
|
* 1. new ComponentName(); // direct class instantiation
|
|
7
|
-
* 2. this.html = r`<div><component-name></component-name></div>; // as a child of another
|
|
7
|
+
* 2. this.html = r`<div><component-name></component-name></div>; // as a child of another Component.
|
|
8
8
|
* 3. <body><component-name></component-name></body> // in the Document html.
|
|
9
9
|
*
|
|
10
10
|
* When created via #3, Solarite has no way to pass attributes as arguments to the constructor. So to make
|
|
@@ -54,19 +54,19 @@ export function getArg(el, name, val=null, type=ArgType.String) {
|
|
|
54
54
|
case ArgType.Float:
|
|
55
55
|
return parseFloat(val);
|
|
56
56
|
case ArgType.String:
|
|
57
|
-
return val
|
|
57
|
+
return [undefined, null, false].includes(val) ? '' : val+'';
|
|
58
58
|
case ArgType.JSON:
|
|
59
|
-
try {
|
|
60
|
-
return JSON.parse(val);
|
|
61
|
-
} catch (e) {
|
|
62
|
-
return val;
|
|
63
|
-
}
|
|
64
59
|
case ArgType.Eval:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
if (typeof val === 'string' && val.length)
|
|
61
|
+
try {
|
|
62
|
+
if (type === ArgType.JSON)
|
|
63
|
+
return JSON.parse(val);
|
|
64
|
+
else
|
|
65
|
+
return eval(`(${val})`);
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return val;
|
|
68
|
+
}
|
|
69
|
+
else return val;
|
|
70
70
|
default:
|
|
71
71
|
return val;
|
|
72
72
|
}
|
package/src/solarite/hash.js
CHANGED
|
@@ -3,32 +3,14 @@ let objectIds = new WeakMap();
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @param obj {Object|string|Node}
|
|
6
|
-
* @param prefix
|
|
7
6
|
* @returns {string} */
|
|
8
|
-
export function getObjectId(obj
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//#IFDEV
|
|
13
|
-
// Slower but useful for debugging:
|
|
14
|
-
if (!prefix) {
|
|
15
|
-
if (Array.isArray(obj))
|
|
16
|
-
prefix = 'Array';
|
|
17
|
-
else if (typeof obj === 'function')
|
|
18
|
-
prefix = 'Func';
|
|
19
|
-
else if (typeof obj === 'object')
|
|
20
|
-
prefix = 'Obj'
|
|
21
|
-
}
|
|
22
|
-
//#ENDIF
|
|
23
|
-
|
|
24
|
-
prefix = prefix || '~\f';
|
|
25
|
-
|
|
7
|
+
export function getObjectId(obj) {
|
|
26
8
|
// if (typeof obj === 'function')
|
|
27
|
-
// return obj.toString();
|
|
9
|
+
// return obj.toString(); // This fails to detect when a function's bound variables changes.
|
|
28
10
|
|
|
29
11
|
let result = objectIds.get(obj);
|
|
30
12
|
if (!result) { // convert to string, store in result, then add 1 to lastObjectId.
|
|
31
|
-
result =
|
|
13
|
+
result = (lastObjectId++); // We use a unique prefix to ensure it doesn't collide w/ strings not from getObjectId()
|
|
32
14
|
objectIds.set(obj, result)
|
|
33
15
|
}
|
|
34
16
|
return result;
|
|
@@ -41,12 +23,20 @@ export function getObjectId(obj, prefix=null) {
|
|
|
41
23
|
* Adding a toJSON method globally on these object prototypes doesn't incur that performance penalty. */
|
|
42
24
|
let isHashing = true;
|
|
43
25
|
function toJSON() {
|
|
44
|
-
//return (isHashing && !Array.isArray(this)) ? getObjectId(this) : this
|
|
45
26
|
return isHashing ? getObjectId(this) : this
|
|
46
27
|
}
|
|
28
|
+
|
|
29
|
+
|
|
47
30
|
// Node.prototype.toJSON = toJSON;
|
|
48
31
|
// Function.prototype.toJSON = toJSON;
|
|
49
|
-
|
|
32
|
+
// Sometimes these get unassigned by Chrome and Brave 119, as well as Firefox, seemingly randomly!
|
|
33
|
+
// The same tests sometimes pass, sometimes fail, even after browser and OS restarts.
|
|
34
|
+
// So we check the assignments on every run of getObjectHash()
|
|
35
|
+
if (Node.prototype.toJSON !== toJSON) {
|
|
36
|
+
Node.prototype.toJSON = toJSON;
|
|
37
|
+
if (Function.prototype.toJSON !== toJSON) // Will it only unmap one but not the other?
|
|
38
|
+
Function.prototype.toJSON = toJSON;
|
|
39
|
+
}
|
|
50
40
|
|
|
51
41
|
/**
|
|
52
42
|
* Get a string that uniquely maps to the values of the given object.
|
|
@@ -55,25 +45,18 @@ function toJSON() {
|
|
|
55
45
|
*
|
|
56
46
|
* Relies on the Node and Function prototypes being overridden above.
|
|
57
47
|
*
|
|
48
|
+
* Note that passing an integer may collide with the number we get from hashing an object.
|
|
49
|
+
* But we don't handle that case because we need max performance and Solarite never passes integers to this function.
|
|
50
|
+
*
|
|
58
51
|
* @param obj {*}
|
|
59
52
|
* @returns {string} */
|
|
60
53
|
export function getObjectHash(obj) {
|
|
61
|
-
|
|
62
|
-
// Sometimes these get unassigned by Chrome and Brave 119, as well as Firefox, seemingly randomly!
|
|
63
|
-
// The same tests sometimes pass, sometimes fail, even after browser and OS restarts.
|
|
64
|
-
// So we check the assignments on every run of getObjectHash()
|
|
65
|
-
if (Node.prototype.toJSON !== toJSON) {
|
|
66
|
-
Node.prototype.toJSON = toJSON;
|
|
67
|
-
if (Function.prototype.toJSON !== toJSON) // Will it only unmap one but not the other?
|
|
68
|
-
Function.prototype.toJSON = toJSON;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
54
|
let result;
|
|
72
55
|
isHashing = true;
|
|
73
56
|
try {
|
|
74
57
|
result = JSON.stringify(obj);
|
|
75
58
|
}
|
|
76
|
-
catch(e){
|
|
59
|
+
catch(e) {
|
|
77
60
|
result = getObjectHashCircular(obj);
|
|
78
61
|
}
|
|
79
62
|
isHashing = false;
|
|
@@ -81,7 +64,7 @@ export function getObjectHash(obj) {
|
|
|
81
64
|
}
|
|
82
65
|
|
|
83
66
|
/**
|
|
84
|
-
*
|
|
67
|
+
* Slower hashing method that supports.
|
|
85
68
|
* @param obj
|
|
86
69
|
* @returns {string} */
|
|
87
70
|
function getObjectHashCircular(obj) {
|
package/src/solarite/r.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Template from "./Template.js";
|
|
2
|
-
import
|
|
2
|
+
import Util from "./Util.js";
|
|
3
|
+
import Globals from "./Globals.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Convert strings to HTMLNodes.
|
|
@@ -14,130 +15,139 @@ import NodeGroupManager from "./NodeGroupManager.js";
|
|
|
14
15
|
* 5. TODO: list more
|
|
15
16
|
*
|
|
16
17
|
* Currently supported:
|
|
17
|
-
* 1. r`<b
|
|
18
|
+
* 1. r(el, options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
|
|
19
|
+
* 2. r(el, template, ?options) // Render the Template created by #1 to element.
|
|
18
20
|
*
|
|
19
|
-
*
|
|
20
|
-
* 3. r(el, options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
|
|
21
|
+
* 3. r`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
|
|
21
22
|
*
|
|
22
23
|
* 4. r('Hello'); // Create single text node.
|
|
23
24
|
* 5. r('<b>Hello</b>'); // Create single HTMLElement
|
|
24
25
|
* 6. r('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
|
|
25
|
-
* 7. r()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which
|
|
26
|
+
* 7. r()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which
|
|
27
|
+
* // includes properly handling nested components and r`` sub-expressions.
|
|
26
28
|
* 8. r(template) // Render Template created by #1.
|
|
27
|
-
* 9. r(() => r`<b>Hello</b>`); // Create dynamic element that has a render() function.
|
|
28
29
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
30
|
+
* 9. r({render(){...}}) // Pass an object with a render method, and optionally other props/methods.
|
|
31
|
+
*
|
|
32
|
+
* @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
|
|
33
|
+
* @param exprs {*[]|string|Template|Object}
|
|
31
34
|
* @return {Node|HTMLElement|Template} */
|
|
32
35
|
export default function r(htmlStrings=undefined, ...exprs) {
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
37
|
+
// TODO: Make this a more flat if/else and call other functions for the logic.
|
|
38
|
+
if (htmlStrings instanceof Node) {
|
|
39
|
+
let parent = htmlStrings, template = exprs[0];
|
|
40
|
+
|
|
41
|
+
// 1
|
|
42
|
+
if (!(exprs[0] instanceof Template)) {
|
|
43
|
+
if (parent.shadowRoot)
|
|
44
|
+
parent.innerHTML = ''; // Remove shadowroot. TODO: This could mess up paths?
|
|
45
|
+
|
|
46
|
+
let options = exprs[0];
|
|
47
|
+
|
|
48
|
+
// Return a tagged template function that applies the tagged themplate to parent.
|
|
49
|
+
let taggedTemplate = (htmlStrings, ...exprs) => {
|
|
50
|
+
Globals.rendered.add(parent)
|
|
51
|
+
let template = new Template(htmlStrings, exprs);
|
|
52
|
+
return template.render(parent, options);
|
|
53
|
+
}
|
|
54
|
+
return taggedTemplate;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. Render template created by #4 to element.
|
|
58
|
+
else if (exprs[0] instanceof Template) {
|
|
59
|
+
let options = exprs[1];
|
|
60
|
+
template.render(parent, options);
|
|
61
|
+
|
|
62
|
+
// Append on the first go.
|
|
63
|
+
if (!parent.childNodes.length && this) {
|
|
64
|
+
// TODO: Is this ever executed?
|
|
65
|
+
debugger;
|
|
66
|
+
parent.append(this.rootNg.getParentNode());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// null for expr[0], remove whole element.
|
|
73
|
+
// This path never happens?
|
|
74
|
+
else {
|
|
75
|
+
throw new Error('unsupported');
|
|
76
|
+
//let ngm = NodeGroupManager.get(parent);
|
|
77
|
+
//ngm.render(null, exprs[1])
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 3. Path if used as a template tag.
|
|
82
|
+
else if (Array.isArray(htmlStrings)) {
|
|
83
|
+
return new Template(htmlStrings, exprs);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
else if (typeof htmlStrings === 'string' || htmlStrings instanceof String) {
|
|
87
|
+
// If it starts with a string, trim both ends.
|
|
88
|
+
// TODO: Also trim if it ends with whitespace?
|
|
89
|
+
if (htmlStrings.match(/^\s^</))
|
|
90
|
+
htmlStrings = htmlStrings.trim();
|
|
91
|
+
|
|
92
|
+
// We create a new one each time because otherwise
|
|
93
|
+
// the returned fragment will have its content replaced by a subsequent call.
|
|
94
|
+
let templateEl = document.createElement('template');
|
|
95
|
+
templateEl.innerHTML = htmlStrings;
|
|
96
|
+
|
|
97
|
+
// 4+5. Return Node if there's one child.
|
|
98
|
+
let relevantNodes = Util.trimEmptyNodes(templateEl.content.childNodes);
|
|
99
|
+
if (relevantNodes.length === 1)
|
|
100
|
+
return relevantNodes[0];
|
|
101
|
+
|
|
102
|
+
// 6. Otherwise return DocumentFragment.
|
|
103
|
+
return templateEl.content;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 7. Create a static element
|
|
107
|
+
else if (htmlStrings === undefined) {
|
|
108
|
+
return (htmlStrings, ...exprs) => {
|
|
109
|
+
//Globals.rendered.add(parent)
|
|
110
|
+
let template = r(htmlStrings, ...exprs);
|
|
111
|
+
return template.render();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 8.
|
|
116
|
+
else if (htmlStrings instanceof Template) {
|
|
117
|
+
return htmlStrings.render();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
// 9. Create dynamic element with render() function.
|
|
122
|
+
else if (typeof htmlStrings === 'object') {
|
|
123
|
+
let obj = htmlStrings;
|
|
124
|
+
|
|
125
|
+
// Special rebound render path, called by normal path.
|
|
126
|
+
if (Globals.objToEl.has(obj)) {
|
|
127
|
+
return function(...args) {
|
|
128
|
+
let template = r(...args);
|
|
129
|
+
let el = template.render();
|
|
130
|
+
Globals.objToEl.set(obj, el);
|
|
131
|
+
}.bind(obj);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Normal path
|
|
135
|
+
else {
|
|
136
|
+
Globals.objToEl.set(obj, null);
|
|
137
|
+
obj.render(); // Calls the Special rebound render path above, when the render function calls r(this)
|
|
138
|
+
let el = Globals.objToEl.get(obj);
|
|
139
|
+
Globals.objToEl.delete(obj);
|
|
140
|
+
|
|
141
|
+
for (let name in obj)
|
|
142
|
+
if (typeof obj[name] === 'function')
|
|
143
|
+
el[name] = obj[name].bind(el);
|
|
144
|
+
else
|
|
145
|
+
el[name] = obj[name];
|
|
146
|
+
|
|
147
|
+
return el;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
else
|
|
152
|
+
throw new Error('Unsupported arguments.')
|
|
153
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trying to be able to automatically watch primitive values.
|
|
3
|
+
* TODO:
|
|
4
|
+
* 1. Have get() return Proxies for nested updates.
|
|
5
|
+
* 2. Override .map() for loops to capture changes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// Example:
|
|
10
|
+
/*
|
|
11
|
+
class WatchExample extends Solarite {
|
|
12
|
+
|
|
13
|
+
constructor(items = []) {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
this.items = items;
|
|
17
|
+
watch(this, 'items');
|
|
18
|
+
|
|
19
|
+
this.name = 'George';
|
|
20
|
+
watch(this, 'name');
|
|
21
|
+
|
|
22
|
+
this.render();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
render() {
|
|
26
|
+
r(this)`
|
|
27
|
+
<watch-example>
|
|
28
|
+
${() => this.name + '!'}
|
|
29
|
+
|
|
30
|
+
${this.items.map(item => r`
|
|
31
|
+
<div>${item.name}</div>
|
|
32
|
+
`)}
|
|
33
|
+
${this.items.length}
|
|
34
|
+
</watch-example>`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
customElements.define('watch-example', WatchExample);
|
|
38
|
+
|
|
39
|
+
let a = new WatchExample();
|
|
40
|
+
|
|
41
|
+
// Items is a Proxy.
|
|
42
|
+
// Calling push() will trigger the map'd ExprPaths to add another at the end.
|
|
43
|
+
// And the .length expression to update.
|
|
44
|
+
// Because accessing .items returns a proxy.
|
|
45
|
+
a.items.push({name: 'Fred'});
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
import Globals from "./Globals.js";
|
|
50
|
+
import delve from "../util/delve.js";
|
|
51
|
+
//import NodeGroupManager from "./NodeGroupManager.js";
|
|
52
|
+
|
|
53
|
+
let unusedArg = Symbol('unusedArg');
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
*
|
|
57
|
+
* @param root {Object}
|
|
58
|
+
* @param path {string}
|
|
59
|
+
* @param value {string|Symbol} */
|
|
60
|
+
export default function watch3(root, path, value=unusedArg) {
|
|
61
|
+
|
|
62
|
+
/** @type {ExprPath} The ExprPath that's using this variable.*/
|
|
63
|
+
let exprPath;
|
|
64
|
+
|
|
65
|
+
/** @type {function} Function evaluated by the ExprPath. */
|
|
66
|
+
let exprFunction;
|
|
67
|
+
|
|
68
|
+
if (value !== unusedArg)
|
|
69
|
+
root[path] = value;
|
|
70
|
+
|
|
71
|
+
// Store internal value used by get/set.
|
|
72
|
+
else
|
|
73
|
+
value = root[path];
|
|
74
|
+
|
|
75
|
+
Object.defineProperty(root, path, {
|
|
76
|
+
get() {
|
|
77
|
+
// Track which ExprPath is using this variable.
|
|
78
|
+
if (Globals.currentExprPath)
|
|
79
|
+
[exprPath, exprFunction] = Globals.currentExprPath;
|
|
80
|
+
|
|
81
|
+
// TODO: Return Proxy for non-primitive value, to track path changed.
|
|
82
|
+
return value;
|
|
83
|
+
},
|
|
84
|
+
set(val) {
|
|
85
|
+
value = val;
|
|
86
|
+
|
|
87
|
+
if (exprFunction) {
|
|
88
|
+
|
|
89
|
+
// TODO: Will fail for attribute w/ a value having multiple ExprPaths.
|
|
90
|
+
// TODO: This won't update a component's expressions.
|
|
91
|
+
exprPath.apply(exprFunction);
|
|
92
|
+
|
|
93
|
+
exprPath.freeNodeGroups(); // TODO: This could be skipped if applyExprs() never marked them as in-use.
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|