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/src/solarite/Shell.js
CHANGED
|
@@ -56,7 +56,7 @@ export default class Shell {
|
|
|
56
56
|
|
|
57
57
|
// Swap out Embedded Solarite Components with ${} attributes.
|
|
58
58
|
// Later, NodeGroup.render() will search for these and replace them with the real components.
|
|
59
|
-
// Ctrl+F "
|
|
59
|
+
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
60
60
|
if (context === htmlContext.Attribute) {
|
|
61
61
|
|
|
62
62
|
let lastIndex, lastMatch;
|
|
@@ -66,7 +66,7 @@ export default class Shell {
|
|
|
66
66
|
})
|
|
67
67
|
|
|
68
68
|
if (lastMatch) {
|
|
69
|
-
let newTagName = lastMatch + '-
|
|
69
|
+
let newTagName = lastMatch + '-solarite-placeholder';
|
|
70
70
|
lastHtml = lastHtml.slice(0, lastIndex) + newTagName + lastHtml.slice(lastIndex + lastMatch.length);
|
|
71
71
|
componentNames[lastMatch] = newTagName
|
|
72
72
|
}
|
|
@@ -85,7 +85,7 @@ export default class Shell {
|
|
|
85
85
|
let template = document.createElement('template'); // Using a single global template won't keep the nodes as children of the DocumentFragment.
|
|
86
86
|
let joinedHtml = buffer.join('');
|
|
87
87
|
|
|
88
|
-
// Replace '-
|
|
88
|
+
// Replace '-solarite-placeholder' close tags.
|
|
89
89
|
// TODO: is there a better way? What if the close tag is inside a comment?
|
|
90
90
|
for (let name in componentNames)
|
|
91
91
|
joinedHtml = joinedHtml.replaceAll(`</${name}>`, `</${componentNames[name]}>`);
|
|
@@ -210,8 +210,8 @@ export default class Shell {
|
|
|
210
210
|
}
|
|
211
211
|
toRemove.map(el => el.remove());
|
|
212
212
|
|
|
213
|
-
// Handle
|
|
214
|
-
// Ctrl+F "
|
|
213
|
+
// Handle solarite-placeholder's.
|
|
214
|
+
// Ctrl+F "solarite-placeholder" in project to find all code that manages subcomponents.
|
|
215
215
|
//if (componentNames.size)
|
|
216
216
|
// this.components = [...this.fragment.querySelectorAll([...componentNames].join(','))]
|
|
217
217
|
|
package/src/solarite/Solarite.js
CHANGED
|
@@ -14,6 +14,9 @@ let Solarite = new Proxy(createSolarite(), {
|
|
|
14
14
|
export {Solarite}
|
|
15
15
|
export {default as r} from './r.js';
|
|
16
16
|
export {getArg, ArgType} from './getArg.js';
|
|
17
|
+
export {default as Template} from './Template.js';
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
//Experimental:
|
|
21
|
+
//export {forEach, watchGet, watchSet} from './watch.js' // old, unfinished
|
|
22
|
+
//export {watch} from './watch2.js'; // unfinished
|
package/src/solarite/Template.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {assert} from "../util/Errors.js";
|
|
2
2
|
import {getObjectId} from "./hash.js";
|
|
3
3
|
import NodeGroupManager from "./NodeGroupManager.js";
|
|
4
|
+
import NodeGroup from "./NodeGroup.js";
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -23,11 +24,11 @@ export default class Template {
|
|
|
23
24
|
/** Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
|
|
24
25
|
hashedFields;
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated
|
|
29
|
+
* @type {ExprPath} Used with forEach() from watch.js
|
|
30
|
+
* Set in ExprPath.apply() */
|
|
31
|
+
parentPath;
|
|
31
32
|
|
|
32
33
|
/** @type {NodeGroup} */
|
|
33
34
|
nodeGroup;
|
|
@@ -71,11 +72,133 @@ export default class Template {
|
|
|
71
72
|
return this.hashedFields
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Render the main template, which may indirectly call renderTemplate() to create children.
|
|
77
|
+
* @param el {HTMLElement}
|
|
78
|
+
* @param options {RenderOptions}
|
|
79
|
+
* @return {?DocumentFragment|HTMLElement} */
|
|
80
|
+
render(el=null, options={}) {
|
|
81
|
+
|
|
82
|
+
let ng;
|
|
83
|
+
if (!el) {
|
|
84
|
+
ng = new NodeGroup(this);
|
|
85
|
+
el = ng.getParentNode();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let ngm = NodeGroupManager.get(el);
|
|
89
|
+
if (ng)
|
|
90
|
+
ng.manager = ngm;
|
|
91
|
+
|
|
92
|
+
//#IFDEV
|
|
93
|
+
ngm.modifications = {
|
|
94
|
+
created: [],
|
|
95
|
+
updated: [],
|
|
96
|
+
moved: [],
|
|
97
|
+
deleted: []
|
|
98
|
+
};
|
|
99
|
+
//#ENDIF
|
|
100
|
+
|
|
101
|
+
ngm.options = options;
|
|
102
|
+
ngm.clearSubscribers = false; // Used for deprecated watch() path?
|
|
103
|
+
ngm.mutationWatcherEnabled = false;
|
|
104
|
+
|
|
105
|
+
// Fast path for empty component.
|
|
106
|
+
if (this.html?.length === 1 && !this.html[0]) {
|
|
107
|
+
el.innerHTML = '';
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
|
|
111
|
+
// Find or create a NodeGroup for the template.
|
|
112
|
+
// This updates all nodes from the template.
|
|
113
|
+
let close;
|
|
114
|
+
let exact = ngm.getNodeGroup(this, true);
|
|
115
|
+
if (!exact) {
|
|
116
|
+
close = ngm.getNodeGroup(this, false);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let firstTime = !ngm.rootNg;
|
|
120
|
+
ngm.rootNg = exact || close;
|
|
121
|
+
|
|
122
|
+
// Reparent NodeGroup
|
|
123
|
+
// TODO: Move this to NodeGroup?
|
|
124
|
+
let parent = ngm.rootNg.getParentNode();
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
// If this is the first time rendering this element.
|
|
128
|
+
if (firstTime) {
|
|
129
|
+
|
|
130
|
+
// Save slot children
|
|
131
|
+
let fragment;
|
|
132
|
+
if (el.childNodes.length) {
|
|
133
|
+
fragment = document.createDocumentFragment();
|
|
134
|
+
fragment.append(...el.childNodes);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Add rendered elements.
|
|
138
|
+
if (parent instanceof DocumentFragment)
|
|
139
|
+
el.append(parent);
|
|
140
|
+
else if (parent)
|
|
141
|
+
el.append(...parent.childNodes)
|
|
142
|
+
|
|
143
|
+
// Apply slot children
|
|
144
|
+
if (fragment) {
|
|
145
|
+
for (let slot of el.querySelectorAll('slot[name]')) {
|
|
146
|
+
let name = slot.getAttribute('name')
|
|
147
|
+
if (name)
|
|
148
|
+
slot.append(...fragment.querySelectorAll(`[slot='${name}']`))
|
|
149
|
+
}
|
|
150
|
+
let unamedSlot = el.querySelector('slot:not([name])')
|
|
151
|
+
if (unamedSlot)
|
|
152
|
+
unamedSlot.append(fragment)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
ngm.rootEl = el;
|
|
157
|
+
|
|
158
|
+
// this.rootNg was rendered as childrenOnly=true
|
|
159
|
+
// Apply attributes from a root element to the real root element.
|
|
160
|
+
let ng = ngm.rootNg;
|
|
161
|
+
if (ng.pseudoRoot && ng.pseudoRoot !== el) {
|
|
162
|
+
/*#IFDEV*/assert(el)/*#ENDIF*/
|
|
163
|
+
|
|
164
|
+
// Remove old attributes
|
|
165
|
+
// for (let attrib of this.rootEl.attributes)
|
|
166
|
+
// if (attrib.name !== 'is' && attrib.name !== 'data-style' && !ng.pseudoRoot.hasAttribute(attrib.name))
|
|
167
|
+
// this.rootEl.removeAttribute(attrib.name)
|
|
168
|
+
|
|
169
|
+
// Add/set new attributes
|
|
170
|
+
if (firstTime)
|
|
171
|
+
for (let attrib of ng.pseudoRoot.attributes)
|
|
172
|
+
if (!el.hasAttribute(attrib.name))
|
|
173
|
+
el.setAttribute(attrib.name, attrib.value);
|
|
174
|
+
|
|
175
|
+
// ng.startNode = ng.endNode = this.rootEl;
|
|
176
|
+
// ng.nodesCache = [ng.startNode]
|
|
177
|
+
// for (let path of ng.paths) {
|
|
178
|
+
// if (path.nodeMarker === ng.rootEl)
|
|
179
|
+
// path.nodeMarker = this.rootEl;
|
|
180
|
+
// path.nodesCache = null;
|
|
181
|
+
// /*#IFDEV*/assert(path.nodeBefore !== ng.rootEl)/*#ENDIF*/
|
|
182
|
+
// }
|
|
183
|
+
//
|
|
184
|
+
// ng.rootEl = this.rootEl;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/*#IFDEV*/ngm.rootNg.verify();/*#ENDIF*/
|
|
188
|
+
ngm.reset(); // Mark all NodeGroups as available, for next render.
|
|
189
|
+
/*#IFDEV*/ngm.rootNg.verify();/*#ENDIF*/
|
|
190
|
+
|
|
191
|
+
window.ngm = ngm;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
ngm.mutationWatcherEnabled = true;
|
|
195
|
+
return el;
|
|
196
|
+
//#IFDEV
|
|
197
|
+
//return ngm.modifications;
|
|
198
|
+
//#ENDIF
|
|
77
199
|
}
|
|
78
200
|
|
|
201
|
+
|
|
79
202
|
getCloseKey() {
|
|
80
203
|
// Use the joined html when debugging?
|
|
81
204
|
//return '@'+this.html.join('|')
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import Util from "../util/Util.js";
|
|
2
|
-
import {assert} from "../util/Errors.js";
|
|
2
|
+
//import {assert} from "../util/Errors.js";
|
|
3
3
|
import delve from "../util/delve.js";
|
|
4
4
|
import {getArg, ArgType} from "./getArg.js";
|
|
5
5
|
import {getObjectHash} from "./hash.js";
|
|
6
6
|
import NodeGroupManager from "./NodeGroupManager.js";
|
|
7
7
|
import r, {rendered} from "./r.js";
|
|
8
8
|
import {camelToDashes} from "./Util.js";
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
//import {watchGet, watchSet} from "./watch.js";
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
function defineClass(Class, tagName, extendsTag) {
|
|
@@ -39,9 +42,15 @@ let connected = new WeakSet();
|
|
|
39
42
|
* 2. Calls render() when added to the DOM, if it hasn't been called already.
|
|
40
43
|
* 3. Child elements are added before constructor is called. But they're also passed to the constructor.
|
|
41
44
|
* 4. We can use this.html = r`...` to set html.
|
|
42
|
-
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
45
|
+
* 5. We have the onConnect, onFirstConnect, and onDisconnect methods.
|
|
46
|
+
* Can't figure out how to have these work standalone though, and still be synchronous.
|
|
43
47
|
* 6. Can we extend from other element types like TR?
|
|
44
48
|
*
|
|
49
|
+
* Advantages to inheriting from HTMLElement
|
|
50
|
+
* 1. Minimization won't break when it renames the Class and we call customElements.define() on the wrong name.
|
|
51
|
+
* 2. We can inherit from things like HTMLTableRowElement directly.
|
|
52
|
+
* 3. There's less magic, since everyone is familiar with defining custom elements.
|
|
53
|
+
*
|
|
45
54
|
* @param extendsTag {?string}
|
|
46
55
|
* @return {Class} */
|
|
47
56
|
export default function createSolarite(extendsTag=null) {
|
|
@@ -105,7 +114,7 @@ export default function createSolarite(extendsTag=null) {
|
|
|
105
114
|
if (ch)
|
|
106
115
|
(this.querySelector('slot') || this).append(...ch);
|
|
107
116
|
|
|
108
|
-
|
|
117
|
+
/** @deprecated */
|
|
109
118
|
Object.defineProperty(this, 'html', {
|
|
110
119
|
set(html) {
|
|
111
120
|
rendered.add(this);
|
|
@@ -155,7 +164,9 @@ export default function createSolarite(extendsTag=null) {
|
|
|
155
164
|
defineClass(this, tagName, extendsTag)
|
|
156
165
|
}
|
|
157
166
|
|
|
167
|
+
//#IFDEV
|
|
158
168
|
|
|
169
|
+
/** @deprecated */
|
|
159
170
|
renderWatched() {
|
|
160
171
|
let ngm = NodeGroupManager.get(this);
|
|
161
172
|
|
|
@@ -229,8 +240,7 @@ export default function createSolarite(extendsTag=null) {
|
|
|
229
240
|
// Create new NodeGroup
|
|
230
241
|
let ng = ngm.getNodeGroup(template, false, true);
|
|
231
242
|
ng.parentPath = beforeNg?.parentPath || loopInfo.template.parentPath;
|
|
232
|
-
|
|
233
|
-
assert(ng.parentPath);/*#ENDIF*/
|
|
243
|
+
|
|
234
244
|
for (let node of ng.getNodes())
|
|
235
245
|
beforeNode.parentNode.insertBefore(node, beforeNode);
|
|
236
246
|
|
|
@@ -260,8 +270,10 @@ export default function createSolarite(extendsTag=null) {
|
|
|
260
270
|
/**
|
|
261
271
|
* @deprecated Use the getArg() function instead. */
|
|
262
272
|
getArg(name, val=null, type=ArgType.String) {
|
|
273
|
+
throw new Error('deprecated');
|
|
263
274
|
return getArg(this, name, val, type);
|
|
264
275
|
}
|
|
276
|
+
//#ENDIF
|
|
265
277
|
}
|
|
266
278
|
}
|
|
267
279
|
|
package/src/solarite/getArg.js
CHANGED
|
@@ -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
package/src/solarite/r.js
CHANGED
|
@@ -24,10 +24,10 @@ import NodeGroupManager from "./NodeGroupManager.js";
|
|
|
24
24
|
* 6. r('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
|
|
25
25
|
* 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.
|
|
26
26
|
* 8. r(template) // Render Template created by #1.
|
|
27
|
-
* 9. r(() => r`<b>Hello</b
|
|
27
|
+
* 9. r(() => r`<b>Hello</b>`, {...}); // Create dynamic element that has a render() function.
|
|
28
28
|
*
|
|
29
29
|
* @param htmlStrings {?HTMLElement|string|string[]|function():Template}
|
|
30
|
-
* @param exprs {*[]|string|Template}
|
|
30
|
+
* @param exprs {*[]|string|Template|Object}
|
|
31
31
|
* @return {Node|HTMLElement|Template} */
|
|
32
32
|
export default function r(htmlStrings=undefined, ...exprs) {
|
|
33
33
|
|
|
@@ -41,9 +41,8 @@ export default function r(htmlStrings=undefined, ...exprs) {
|
|
|
41
41
|
|
|
42
42
|
// 2. Render template created by #4 to element.
|
|
43
43
|
if (exprs[0] instanceof Template) {
|
|
44
|
-
let ngm = NodeGroupManager.get(parent);
|
|
45
44
|
let options = exprs[1];
|
|
46
|
-
|
|
45
|
+
template.render(parent, options);
|
|
47
46
|
|
|
48
47
|
// Append on the first go.
|
|
49
48
|
if (!parent.childNodes.length && this) {
|
|
@@ -62,15 +61,16 @@ export default function r(htmlStrings=undefined, ...exprs) {
|
|
|
62
61
|
return (htmlStrings, ...exprs) => {
|
|
63
62
|
rendered.add(parent)
|
|
64
63
|
let template = r(htmlStrings, ...exprs);
|
|
65
|
-
|
|
66
|
-
return ngm.render(template, options);
|
|
64
|
+
return template.render(parent, options);
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
// null for expr[0], remove whole element.
|
|
69
|
+
// This path never happens?
|
|
71
70
|
else {
|
|
72
|
-
|
|
73
|
-
ngm.
|
|
71
|
+
throw new Error('unsupported');
|
|
72
|
+
//let ngm = NodeGroupManager.get(parent);
|
|
73
|
+
//ngm.render(null, exprs[1])
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -98,16 +98,16 @@ export default function r(htmlStrings=undefined, ...exprs) {
|
|
|
98
98
|
return (htmlStrings, ...exprs) => {
|
|
99
99
|
//rendered.add(parent)
|
|
100
100
|
let template = r(htmlStrings, ...exprs);
|
|
101
|
-
return template.
|
|
101
|
+
return template.render();
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// 8.
|
|
106
106
|
else if (htmlStrings instanceof Template) {
|
|
107
|
-
|
|
108
|
-
return ngm.render(htmlStrings);
|
|
107
|
+
return htmlStrings.render();
|
|
109
108
|
}
|
|
110
109
|
|
|
110
|
+
|
|
111
111
|
// 9. Create dynamic element with render() function.
|
|
112
112
|
else if (typeof htmlStrings === 'function') {
|
|
113
113
|
let getTemplate = htmlStrings;
|
|
@@ -116,17 +116,27 @@ export default function r(htmlStrings=undefined, ...exprs) {
|
|
|
116
116
|
if (typeof template === 'string')
|
|
117
117
|
throw new Error(`Please add the "r" prefix before the string "${template}"`)
|
|
118
118
|
|
|
119
|
-
let ngm = new NodeGroupManager();
|
|
120
119
|
template.replaceMode = true;
|
|
121
|
-
let el =
|
|
120
|
+
let el = template.render();
|
|
122
121
|
|
|
122
|
+
// Create the render() function from the function we were given.
|
|
123
123
|
el.render = (function() {
|
|
124
124
|
template = getTemplate();
|
|
125
|
-
|
|
125
|
+
template.render(el);
|
|
126
126
|
}).bind(el);
|
|
127
127
|
|
|
128
|
+
|
|
129
|
+
// The second argument was an object of additional properties to add.
|
|
130
|
+
let props = exprs[0];
|
|
131
|
+
for (let name in props)
|
|
132
|
+
if (typeof props[name] === 'function')
|
|
133
|
+
el[name] = props[name].bind(el);
|
|
134
|
+
else
|
|
135
|
+
el[name] = props[name];
|
|
136
|
+
|
|
128
137
|
return el;
|
|
129
138
|
}
|
|
139
|
+
|
|
130
140
|
else
|
|
131
141
|
throw new Error('Unsupported arguments.')
|
|
132
142
|
}
|
package/src/solarite/watch.js
CHANGED
|
@@ -136,7 +136,7 @@ export function forEach(arrayPath, callback) {
|
|
|
136
136
|
];
|
|
137
137
|
|
|
138
138
|
// We return a template that wraps the array
|
|
139
|
-
// So that
|
|
139
|
+
// So that ExprPath.apply() can set the ExprPath and nextSibling on the template.
|
|
140
140
|
// Then the 'insert' path in renderWatched() uses that data fora dding more nodes.
|
|
141
141
|
let result = new Template(['', ''], [newItems])
|
|
142
142
|
|
package/src/solarite/watch2.js
CHANGED
|
@@ -11,6 +11,7 @@ export {logGets, gets}
|
|
|
11
11
|
|
|
12
12
|
let withinSet = 0;
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
/**
|
|
15
16
|
* Turn the props on obj into JavasCript properties that return Proxies when accessed.
|
|
16
17
|
* If called more than once, return the already-converted object.
|
|
@@ -291,7 +292,7 @@ class ProxyHandler {
|
|
|
291
292
|
let obj2 = obj === this.root && this.props ? this.props : obj;
|
|
292
293
|
let result = Reflect.get(obj2, prop);
|
|
293
294
|
|
|
294
|
-
// This is read by watchFunction() which is called in
|
|
295
|
+
// This is read by watchFunction() which is called in ExprPath.apply().
|
|
295
296
|
// It's used to see what variables contribute to an expression.
|
|
296
297
|
if (logGets)
|
|
297
298
|
gets.push([this.root, ...this.path, prop]);
|
|
@@ -392,7 +393,7 @@ function getProxy(obj, ph, path, path2) {
|
|
|
392
393
|
|
|
393
394
|
/**
|
|
394
395
|
* Call a function and record which watched variables it accesess, storing their paths in pathToTemplates.
|
|
395
|
-
* Used by
|
|
396
|
+
* Used by ExprPath.apply().
|
|
396
397
|
* TODO: only allow this to be called once per callback.
|
|
397
398
|
* @param callback {function}
|
|
398
399
|
* @param ngm {NodeGroupManager}
|
|
@@ -0,0 +1,91 @@
|
|
|
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 NodeGroupManager from "./NodeGroupManager.js";
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export default function watch3(root, path) {
|
|
54
|
+
|
|
55
|
+
/** @type {ExprPath} The ExprPath that's using this variable.*/
|
|
56
|
+
let exprPath;
|
|
57
|
+
|
|
58
|
+
/** @type {function} Function evaluated by the ExprPath. */
|
|
59
|
+
let exprFunction;
|
|
60
|
+
|
|
61
|
+
// Store internal value used by get/set.
|
|
62
|
+
let value = root[path];
|
|
63
|
+
|
|
64
|
+
let ngm = NodeGroupManager.get(root);
|
|
65
|
+
|
|
66
|
+
Object.defineProperty(root, path, {
|
|
67
|
+
get() {
|
|
68
|
+
// Track which ExprPath is using this variable.
|
|
69
|
+
if (Globals.currentExprPath)
|
|
70
|
+
[exprPath, exprFunction] = Globals.currentExprPath;
|
|
71
|
+
|
|
72
|
+
// TODO: Return Proxy for non-primitive value, to track path changed.
|
|
73
|
+
return value;
|
|
74
|
+
},
|
|
75
|
+
set(val) {
|
|
76
|
+
value = val;
|
|
77
|
+
|
|
78
|
+
if (exprFunction) {
|
|
79
|
+
let ng = exprPath.parentNg;
|
|
80
|
+
|
|
81
|
+
ng.applyExprs([exprFunction], [exprPath]); // TODO: Will fail for attribute w/ a value having multiple ExprPaths.
|
|
82
|
+
|
|
83
|
+
// TODO: This doesn't cascade upward.
|
|
84
|
+
//ng.exactKey = getObjectHash(ng.template);
|
|
85
|
+
|
|
86
|
+
ngm.reset(); // TODO: This could be skipped if applyExprs() never marked them as in-use.
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// TODO: Everything here fails because our own connectedCallback function is never called.
|
|
2
|
+
// Perhaps b/c these callbacks must be defined before we register the custom element.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @type {Map<HTMLElement, Set<function(HTMLElement)>>} */
|
|
7
|
+
let connectCallbacks = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* @type {Map<HTMLElement, Set<function(HTMLElement)>>} */
|
|
10
|
+
let firstConnectCallbacks = new Map();
|
|
11
|
+
/**
|
|
12
|
+
* @type {Map<HTMLElement, Set<function(HTMLElement)>>} */
|
|
13
|
+
let disconnectCallbacks = new Map();
|
|
14
|
+
|
|
15
|
+
function getElCallbacks(el, map) {
|
|
16
|
+
let callbacks = connectCallbacks.get(el);
|
|
17
|
+
if (!callbacks) {
|
|
18
|
+
callbacks = new Set();
|
|
19
|
+
connectCallbacks.set(el, callbacks);
|
|
20
|
+
}
|
|
21
|
+
return callbacks;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let createConnectedCallback = el => () => {
|
|
25
|
+
debugger;
|
|
26
|
+
for (let cb of getElCallbacks(el, connectCallbacks))
|
|
27
|
+
cb();
|
|
28
|
+
|
|
29
|
+
let cbs = getElCallbacks(el, firstConnectCallbacks);
|
|
30
|
+
for (let cb of cbs) {
|
|
31
|
+
cb();
|
|
32
|
+
cbs.delete(cb);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
* @param el {HTMLElement}
|
|
40
|
+
* @param callback {function}
|
|
41
|
+
* @return {function()} A function that removes the callback. */
|
|
42
|
+
export function onConnect(el, callback) {
|
|
43
|
+
let callbacks = getElCallbacks(el, connectCallbacks);
|
|
44
|
+
callbacks.add(callback);
|
|
45
|
+
|
|
46
|
+
if (!el.constructor.prototype.hasOwnProperty('connectedCallback'))
|
|
47
|
+
el.constructor.prototype.connectedCallback = createConnectedCallback(el);
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
callbacks.delete(callback);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function onFirstConnect(el, callback) {
|
|
55
|
+
let callbacks = getElCallbacks(el, firstConnectCallbacks);
|
|
56
|
+
callbacks.add(callback);
|
|
57
|
+
|
|
58
|
+
if (!el.hasOwnProperty('connectedCallback'))
|
|
59
|
+
el.connectedCallback = createConnectedCallback(el);
|
|
60
|
+
|
|
61
|
+
return () => {
|
|
62
|
+
callbacks.delete(callback);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function onDisconnect(el, callback) {
|
|
67
|
+
let callbacks = getElCallbacks(el, disconnectCallbacks);
|
|
68
|
+
callbacks.add(callback);
|
|
69
|
+
|
|
70
|
+
if (!el.hasOwnProperty('connectedCallback'))
|
|
71
|
+
el.connectedCallback = () => {
|
|
72
|
+
for (let cb of callbacks)
|
|
73
|
+
cb();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return () => {
|
|
77
|
+
callbacks.delete(callback);
|
|
78
|
+
}
|
|
79
|
+
}
|