solarite 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Solarite-debug.js +1457 -1402
- package/dist/Solarite.js +1425 -1272
- package/dist/Solarite.min.js +2 -2
- package/package.json +5 -6
- package/readme.md +2 -4
- package/src/{solarite/ExprPath.js → ExprPath.js} +421 -231
- package/src/Globals.js +79 -0
- package/src/HtmlParser.js +91 -0
- package/src/{util/MultiValueMap.js → MultiValueMap.js} +22 -26
- package/src/{solarite/NodeGroup.js → NodeGroup.js} +286 -226
- package/src/{solarite/Shell.js → Shell.js} +119 -92
- package/src/Solarite.d.ts +62 -0
- package/src/{solarite/Solarite.js → Solarite.js} +15 -13
- package/src/{solarite/Template.js → Template.js} +22 -19
- package/src/Util.js +330 -0
- package/src/{util/Errors.js → assert.js} +1 -0
- package/src/createSolarite.js +154 -0
- package/src/{util/delve.js → delve.js} +5 -4
- package/src/{solarite/getArg.js → getArg.js} +41 -15
- package/src/{solarite/r.js → h.js} +59 -29
- package/src/{solarite/hash.js → hash.js} +12 -9
- package/src/unused/FastLookupArray.js +54 -0
- package/src/unused/Hashes.js +339 -0
- package/src/unused/InUse.test.js +92 -0
- package/src/unused/InUseMap.js +98 -0
- package/src/unused/LinkedList.js +117 -0
- package/src/unused/LinkedList.test.js +115 -0
- package/src/unused/Misc.js +13 -0
- package/src/unused/Perf.js +47 -0
- package/src/unused/TrackedArray.js +54 -0
- package/src/watch.js +546 -0
- package/src/solarite/Globals.js +0 -54
- package/src/solarite/Util.js +0 -388
- package/src/solarite/createSolarite.js +0 -274
- package/src/solarite/watch3.js +0 -189
- package/src/util/Util.js +0 -113
- /package/src/{solarite/udomdiff.js → udomdiff.js} +0 -0
- /package/src/{util → unused}/WeakArray.js +0 -0
package/src/watch.js
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
*
|
|
4
|
+
* TODO:
|
|
5
|
+
* 1. Have option to automatically render?
|
|
6
|
+
*
|
|
7
|
+
* Limitations:
|
|
8
|
+
* 1. If we use one path to get a property during render, but a different path to set it, it will not be marked for rendering.
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// Example:
|
|
15
|
+
/*
|
|
16
|
+
class WatchExample extends Solarite {
|
|
17
|
+
|
|
18
|
+
constructor(items = []) {
|
|
19
|
+
super();
|
|
20
|
+
|
|
21
|
+
this.items = items;
|
|
22
|
+
watch(this, 'items');
|
|
23
|
+
|
|
24
|
+
this.name = 'George';
|
|
25
|
+
watch(this, 'name');
|
|
26
|
+
|
|
27
|
+
this.render();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render() {
|
|
31
|
+
r(this)`
|
|
32
|
+
<watch-example>
|
|
33
|
+
${() => this.name + '!'}
|
|
34
|
+
|
|
35
|
+
${this.items.map(item => r`
|
|
36
|
+
<div>${item.name}</div>
|
|
37
|
+
`)}
|
|
38
|
+
${() => this.items.length}
|
|
39
|
+
</watch-example>`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
customElements.define('watch-example', WatchExample);
|
|
43
|
+
|
|
44
|
+
let a = new WatchExample();
|
|
45
|
+
|
|
46
|
+
// Items is a Proxy.
|
|
47
|
+
// Calling push() will trigger the map'd ExprPaths to add another at the end.
|
|
48
|
+
// And the .length expression to update.
|
|
49
|
+
// Because accessing .items returns a proxy.
|
|
50
|
+
a.items.push({name: 'Fred'});
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
import Globals from "./Globals.js";
|
|
54
|
+
import Util from "./Util.js";
|
|
55
|
+
import {assert} from "./assert.js";
|
|
56
|
+
|
|
57
|
+
let unusedArg = Symbol('unusedArg');
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
function removeProxy(obj) {
|
|
62
|
+
if (obj && obj.$removeProxy)
|
|
63
|
+
return obj.$removeProxy;
|
|
64
|
+
return obj;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Render the ExprPaths that were added to rootNg.exprsToRender.
|
|
69
|
+
* @param root {HTMLElement}
|
|
70
|
+
* @param trackModified {boolean}
|
|
71
|
+
* @returns {Node[]} Modified elements. */
|
|
72
|
+
export function renderWatched(root, trackModified=false) {
|
|
73
|
+
let rootNg = Globals.nodeGroups.get(root);
|
|
74
|
+
let modified;
|
|
75
|
+
|
|
76
|
+
if (trackModified)
|
|
77
|
+
modified = new Set();
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
// Mark NodeGroups of expressionpaths as freed.
|
|
81
|
+
// for (let [exprPath, ops] of rootNg.exprsToRender) {
|
|
82
|
+
// if (ops instanceof WholeArrayOp) {}
|
|
83
|
+
// else if (ops instanceof ValueOp) {}
|
|
84
|
+
// else {} // Array Slice Op
|
|
85
|
+
// }
|
|
86
|
+
|
|
87
|
+
for (let [exprPath, ops] of rootNg.exprsToRender) {
|
|
88
|
+
|
|
89
|
+
// Reapply the whole expression.
|
|
90
|
+
if (ops instanceof WholeArrayOp) {
|
|
91
|
+
|
|
92
|
+
// So it doesn't use the old value inside the map callback in the get handler above.
|
|
93
|
+
// TODO: Find a more sensible way to pass newValue.
|
|
94
|
+
ops.markNodeGroupsAvailable(exprPath);
|
|
95
|
+
exprPath.watchFunction.newValue = ops.array;
|
|
96
|
+
exprPath.apply([exprPath.watchFunction], false);
|
|
97
|
+
|
|
98
|
+
//exprPath.freeNodeGroups();
|
|
99
|
+
|
|
100
|
+
if (trackModified)
|
|
101
|
+
modified.add(...exprPath.getNodes());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Update a single value in a map callback
|
|
105
|
+
// TODO: Why is this not an array of ops?
|
|
106
|
+
else if (ops instanceof ValueOp) {
|
|
107
|
+
|
|
108
|
+
// TODO: I need to only free node groups of watched expressions.
|
|
109
|
+
exprPath.watchFunction.newValue = ops.value;
|
|
110
|
+
exprPath.apply([exprPath.watchFunction], false); // False to not free nodeGroups.
|
|
111
|
+
|
|
112
|
+
//exprPath.freeNodeGroups();
|
|
113
|
+
|
|
114
|
+
if (trackModified)
|
|
115
|
+
modified.add(...exprPath.getNodes());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Selectively update NodeGroups created by array.map()
|
|
119
|
+
else {
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < ops.length; i++) {
|
|
122
|
+
let op = ops[i];
|
|
123
|
+
let nextOp = ops[i + 1];
|
|
124
|
+
|
|
125
|
+
// If we have two Adjacent ArraySpliceOps that swap eachother's items,
|
|
126
|
+
// then be fast by directly swap their DOM nodes.
|
|
127
|
+
if (nextOp instanceof ArraySpliceOp && nextOp.deleteCount === 1 && nextOp.items.length === 1
|
|
128
|
+
&& op instanceof ArraySpliceOp && op.deleteCount === 1 && op.items.length === 1
|
|
129
|
+
&& nextOp.array[nextOp.index] === op.firstDeleted
|
|
130
|
+
&& op.array[op.index] === nextOp.firstDeleted
|
|
131
|
+
) {
|
|
132
|
+
|
|
133
|
+
let nga = exprPath.nodeGroups[op.index];
|
|
134
|
+
let ngb = exprPath.nodeGroups[nextOp.index];
|
|
135
|
+
|
|
136
|
+
// Swap the nodegroup nga and ngb node positions
|
|
137
|
+
let nextA = nga.endNode.nextSibling;
|
|
138
|
+
let nextB = ngb.endNode.nextSibling;
|
|
139
|
+
for (let node of nga.getNodes()) // TODO: Manually iterate instead of calling getNodes().
|
|
140
|
+
node.parentNode.insertBefore(node, nextB);
|
|
141
|
+
for (let node of ngb.getNodes())
|
|
142
|
+
node.parentNode.insertBefore(node, nextA);
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
// replaceWidth version:
|
|
146
|
+
let nextB = ngb.endNode.nextSibling;
|
|
147
|
+
|
|
148
|
+
let ngaNodes = nga.getNodes();
|
|
149
|
+
let ngbNodes = ngb.getNodes();
|
|
150
|
+
let len = Math.min(ngaNodes.length, ngbNodes.length);
|
|
151
|
+
|
|
152
|
+
for (let i=0; i< len; i++)
|
|
153
|
+
ngaNodes[i].replaceWith(ngbNodes[i]);
|
|
154
|
+
// TODO: Insert additional nodes here.
|
|
155
|
+
for (let node of nga.getNodes())
|
|
156
|
+
nextB.parentNode.insertBefore(node, nextB);
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
exprPath.nodeGroups[op.index] = ngb;
|
|
160
|
+
exprPath.nodeGroups[nextOp.index] = nga;
|
|
161
|
+
|
|
162
|
+
if (trackModified) {
|
|
163
|
+
nga.getNodes().map(n => modified.add(n));
|
|
164
|
+
ngb.getNodes().map(n => modified.add(n));
|
|
165
|
+
}
|
|
166
|
+
i++;// skip next op
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ArraySpliceOp
|
|
170
|
+
else { // (op instanceof ArraySpliceOp) {
|
|
171
|
+
|
|
172
|
+
if (trackModified && op.deleteCount)
|
|
173
|
+
modified.add(
|
|
174
|
+
...exprPath.nodeGroups.slice(op.index, op.index + op.deleteCount).map(ng => ng.getNodes()).flat()
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
op.markNodeGroupsAvailable(exprPath);
|
|
178
|
+
exprPath.applyArrayOp(op);
|
|
179
|
+
|
|
180
|
+
if (trackModified && op.items.length) {
|
|
181
|
+
exprPath.nodeGroups.slice(op.index, op.index + op.items.length)
|
|
182
|
+
.map(ng => ng.getNodes())
|
|
183
|
+
.flat()
|
|
184
|
+
.map(n => modified.add(n));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
rootNg.exprsToRender = new Map(); // clear
|
|
191
|
+
|
|
192
|
+
if (trackModified)
|
|
193
|
+
return [...modified];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Passed as an argument when creating a new Proxy().
|
|
210
|
+
* Handles getting and setting properties on the proxied object. */
|
|
211
|
+
class ProxyHandler {
|
|
212
|
+
|
|
213
|
+
/** @type {Record<string, [Proxy, ProxyHandler]>} Proxies for child properties. */
|
|
214
|
+
proxies = {}
|
|
215
|
+
|
|
216
|
+
/** @type Set<ExprPath> ExprPaths that will need to be re-rendered when this variable is modified. */
|
|
217
|
+
exprPaths = new Set();
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* ExprPaths that will need to be re-rendered when one of this variable's primitive properties is modified,
|
|
221
|
+
* since primitives can't have their own ProxyHandler.
|
|
222
|
+
* @type {Record<prop:string, affected:Set<ExprPath>>} */
|
|
223
|
+
childExprPaths = {};
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
constructor(root, value) {
|
|
227
|
+
|
|
228
|
+
/** @type {Object} The top level object being proxied. */
|
|
229
|
+
this.root = root;
|
|
230
|
+
|
|
231
|
+
/** @type {*} the value found when starting at root and following the path? */
|
|
232
|
+
this.value = value;
|
|
233
|
+
|
|
234
|
+
/** @type {RootNodeGroup} Cached, to save time on lookups. */
|
|
235
|
+
this.rootNodeGroup = null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get a cached proxy of a sub-property.
|
|
240
|
+
* @param prop {string}
|
|
241
|
+
* @param val {*}
|
|
242
|
+
* @returns {[Proxy, ProxyHandler]} */
|
|
243
|
+
getProxyandHandler(prop, val) {
|
|
244
|
+
let result = this.proxies[prop];
|
|
245
|
+
if (!result) {
|
|
246
|
+
let handler = new ProxyHandler(this.root, this.value);
|
|
247
|
+
result = this.proxies[prop] = [new Proxy(val, handler), handler];
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* We override get() so we can mark which ExprPaths read from each variable in the hierarchy.
|
|
254
|
+
* Then later when we call set on a variable, we can see which ExprPaths use it, and can mark them to be re-rendered.
|
|
255
|
+
* @param obj
|
|
256
|
+
* @param prop {string}
|
|
257
|
+
* @param receiver
|
|
258
|
+
* @returns {*|Proxy|(function(*): function(): any)} */
|
|
259
|
+
get(obj, prop, receiver) {
|
|
260
|
+
|
|
261
|
+
if (prop === '$removeProxy')
|
|
262
|
+
return obj;
|
|
263
|
+
|
|
264
|
+
// if (prop === 'items')
|
|
265
|
+
// debugger;
|
|
266
|
+
|
|
267
|
+
const result = (obj === receiver)
|
|
268
|
+
? this.value // top-level value.
|
|
269
|
+
: Reflect.get(obj, prop, receiver); // avoid infinite recursion.
|
|
270
|
+
|
|
271
|
+
// We override the map() function the first time render() is called.
|
|
272
|
+
// But it's not re-overridden when we call renderWatched()
|
|
273
|
+
if (Array.isArray(obj)) {
|
|
274
|
+
|
|
275
|
+
if (prop === 'map') {
|
|
276
|
+
|
|
277
|
+
const self = this;
|
|
278
|
+
|
|
279
|
+
// This outer function is so the ExprPath calls it as a function,
|
|
280
|
+
// instead of it being evaluated immediately when the Template is created.
|
|
281
|
+
// This allows ExprPath.apply() to set the Globals.currentExprPath before evaluating further.
|
|
282
|
+
return (callback) =>
|
|
283
|
+
|
|
284
|
+
// This is the new map function.
|
|
285
|
+
function mapFunction() {
|
|
286
|
+
|
|
287
|
+
// Save the ExprPaths that called the array used by .map()
|
|
288
|
+
const currExprPath = Globals.currentExprPath;
|
|
289
|
+
if (currExprPath)
|
|
290
|
+
self.exprPaths.add(currExprPath);
|
|
291
|
+
|
|
292
|
+
// Apply the map function.
|
|
293
|
+
const newObj = mapFunction.newValue || obj;
|
|
294
|
+
Globals.currentExprPath.mapCallback = callback;
|
|
295
|
+
// If new Proxy fails b/c newObj isn't an object, make sure the expression is a function.
|
|
296
|
+
// TODO: Find a way to warn about this automatically.
|
|
297
|
+
let p = new Proxy(newObj, self);
|
|
298
|
+
return Array.prototype.map.call(p, callback);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
else if (prop === 'push' || prop==='pop' || prop === 'splice') {
|
|
303
|
+
const rootNg = Globals.nodeGroups.get(this.root);
|
|
304
|
+
return new WatchedArray(rootNg, obj, this.exprPaths)[prop];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
// Save the ExprPath that's currently accessing this variable.
|
|
310
|
+
const currExprPath = Globals.currentExprPath;
|
|
311
|
+
|
|
312
|
+
// Accessing a sub-property
|
|
313
|
+
if (result && typeof result === 'object') {
|
|
314
|
+
let [proxiedResult, handler] = this.getProxyandHandler(prop, result); // Clone this handler and append prop to the path.
|
|
315
|
+
|
|
316
|
+
if (currExprPath && prop !== 'constructor')
|
|
317
|
+
handler.exprPaths.add(currExprPath);
|
|
318
|
+
|
|
319
|
+
return proxiedResult;
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
if (currExprPath && prop !== 'constructor') {
|
|
323
|
+
|
|
324
|
+
// We can't have Proxies on primitive types,
|
|
325
|
+
// So we store the affected expressions in the parent Proxy.
|
|
326
|
+
if (!this.childExprPaths[prop])
|
|
327
|
+
this.childExprPaths[prop] = new Set([currExprPath]);
|
|
328
|
+
else
|
|
329
|
+
this.childExprPaths[prop].add(currExprPath);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// TODO: Will fail for attribute w/ a value having multiple ExprPaths.
|
|
337
|
+
// TODO: This won't update a component's expressions.
|
|
338
|
+
set(obj, prop, val, receiver) {
|
|
339
|
+
|
|
340
|
+
val = removeProxy(val);
|
|
341
|
+
|
|
342
|
+
// 1. Add to the list of ExprPaths to re-render.
|
|
343
|
+
if (!this.rootNodeGroup)
|
|
344
|
+
this.rootNodeGroup = Globals.nodeGroups.get(this.root);
|
|
345
|
+
const rootNg = this.rootNodeGroup
|
|
346
|
+
|
|
347
|
+
// New: // TODO: Should I instead be checking if the old value of val is a primitive?
|
|
348
|
+
let isPrimitive = !val || typeof val !== 'object';
|
|
349
|
+
let exprPaths = isPrimitive
|
|
350
|
+
? this.childExprPaths[prop] || []
|
|
351
|
+
: this.getProxyandHandler(prop, val)[1].exprPaths;
|
|
352
|
+
|
|
353
|
+
const isArray = Array.isArray(obj);
|
|
354
|
+
for (let exprPath of exprPaths) {
|
|
355
|
+
|
|
356
|
+
if (isArray) {
|
|
357
|
+
if (Number.isInteger(+prop)) {
|
|
358
|
+
const exprsToRender = rootNg.exprsToRender.get(exprPath);
|
|
359
|
+
|
|
360
|
+
// If we're not re-rendering the whole thing.
|
|
361
|
+
if (!(exprsToRender instanceof WholeArrayOp))
|
|
362
|
+
// TODO: Inline this for performance
|
|
363
|
+
Util.mapArrayAdd(rootNg.exprsToRender, exprPath, new ArraySpliceOp(obj, prop, 1, [val]));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Reapply the whole expression.
|
|
367
|
+
else
|
|
368
|
+
rootNg.exprsToRender.set(exprPath, new WholeArrayOp(val));
|
|
369
|
+
}
|
|
370
|
+
else
|
|
371
|
+
rootNg.exprsToRender.set(exprPath, new ValueOp(val));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 2. Set the value.
|
|
375
|
+
if (obj === receiver)
|
|
376
|
+
this.value = val; // top-level value.
|
|
377
|
+
else // Set the value while avoiding infinite recursion.
|
|
378
|
+
Reflect.set(obj, prop, val, receiver);
|
|
379
|
+
|
|
380
|
+
// Value changed, so reset cached proxy.
|
|
381
|
+
if (val && typeof val === 'object')
|
|
382
|
+
delete this.proxies[prop];
|
|
383
|
+
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* This function markes a property of a web component to be watched for changes.
|
|
390
|
+
*
|
|
391
|
+
* Here is how watches work:
|
|
392
|
+
* 1. When we call watch() it creates properties on the root object that return Proxies to watch when values are set.
|
|
393
|
+
* 2. When they are set, we add their paths to the rootNodeGroup.exprsToRender that keeps track of what to re-render.
|
|
394
|
+
* 3. Then we call renderWatched() to re-render only those parts.
|
|
395
|
+
*
|
|
396
|
+
* In more detail:
|
|
397
|
+
* TODO
|
|
398
|
+
*
|
|
399
|
+
*
|
|
400
|
+
* @param root {HTMLElement} An instance of a Web Component that uses r() to render its content.
|
|
401
|
+
* @param field {string} The name of a top-level property of root.
|
|
402
|
+
* @param value {string|Symbol} The default value. */
|
|
403
|
+
export default function watch(root, field, value=unusedArg) {
|
|
404
|
+
// Store internal value used by get/set.
|
|
405
|
+
if (value !== unusedArg)
|
|
406
|
+
root[field] = value;
|
|
407
|
+
else
|
|
408
|
+
value = root[field];
|
|
409
|
+
|
|
410
|
+
let handler = new ProxyHandler(root, value);
|
|
411
|
+
Object.defineProperty(root, field, {
|
|
412
|
+
get: () => handler.get(root, field, root),
|
|
413
|
+
set: (val) => handler.set(root, field, val, root)
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
export {watch};
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Wrap an array so that functions that modify the array are intercepted.
|
|
421
|
+
* We then add ArraySpliceOp's to the list of ops to run for each affected ExprPath.
|
|
422
|
+
* When renderWatched() is called it then applies those ops to the NodeGroups created by the map() function. */
|
|
423
|
+
class WatchedArray {
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* @param array {Array}
|
|
427
|
+
* @param rootNg {RootNodeGroup}
|
|
428
|
+
* @param exprPaths {ExprPath[]|Set<ExprPath>} Expression paths that use this array. */
|
|
429
|
+
constructor(rootNg, array, exprPaths) {
|
|
430
|
+
this.rootNg = rootNg;
|
|
431
|
+
//#IFDEV
|
|
432
|
+
assert(Array.isArray(array));
|
|
433
|
+
//#ENDIF
|
|
434
|
+
this.array = array;
|
|
435
|
+
this.exprPaths = exprPaths;
|
|
436
|
+
this.push = this.push.bind(this);
|
|
437
|
+
this.pop = this.pop.bind(this);
|
|
438
|
+
this.splice = this.splice.bind(this);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
push(...args) {
|
|
442
|
+
return this.internalSplice('push', args, [this.array, this.array.length, 0, args]);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
pop() {
|
|
446
|
+
if (this.array.length)
|
|
447
|
+
return this.internalSplice('pop', [], [this.array, this.array.length-1, 1]);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
splice(...args) {
|
|
451
|
+
return this.internalSplice('splice', args, [this.array, ...args]);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
internalSplice(func, args, spliceArgs) {
|
|
455
|
+
// Mark all expressions affected by the array function to be re-rendered
|
|
456
|
+
for (let exprPath of this.exprPaths) {
|
|
457
|
+
let exprsToRender = this.rootNg.exprsToRender.get(exprPath);
|
|
458
|
+
if (!(exprsToRender instanceof WholeArrayOp)) // If we're not already going to re-render the whole array.
|
|
459
|
+
Util.mapArrayAdd(this.rootNg.exprsToRender, exprPath, new ArraySpliceOp(...spliceArgs));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Call original array function
|
|
463
|
+
return Array.prototype[func].call(this.array, ...args);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class WatchOp {}
|
|
470
|
+
|
|
471
|
+
export class ArraySpliceOp extends WatchOp {
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Represents a splice operation (insertion, deletion, or replacement of elements)
|
|
475
|
+
* to be applied to an array during rendering.
|
|
476
|
+
*
|
|
477
|
+
* @param array {Array} The array affected by the splice operation.
|
|
478
|
+
* @param index {int} The starting index of the splice operation.
|
|
479
|
+
* @param deleteCount {int} The number of elements to delete from the array.
|
|
480
|
+
* @param items {Array} The elements to insert into the array at the starting index. */
|
|
481
|
+
constructor(array, index, deleteCount, items=[]) {
|
|
482
|
+
super();
|
|
483
|
+
//#IFDEV
|
|
484
|
+
assert(Array.isArray(array));
|
|
485
|
+
//#ENDIF
|
|
486
|
+
this.array = array;
|
|
487
|
+
this.index = index*1;
|
|
488
|
+
this.deleteCount = deleteCount;
|
|
489
|
+
this.items = items;
|
|
490
|
+
|
|
491
|
+
// Save the first item deleted so we can see if this should be turned into an ArraySwapOp later.
|
|
492
|
+
this.firstDeleted = deleteCount===1 ? array[index] : undefined;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
markNodeGroupsAvailable(exprPath) {
|
|
496
|
+
if (this.deleteCount > 0) {
|
|
497
|
+
let count = this.index+this.deleteCount;
|
|
498
|
+
for (let i=this.index; i<count; i++) {
|
|
499
|
+
let oldNg = exprPath.nodeGroups[i];
|
|
500
|
+
exprPath.nodeGroupsAttachedAvailable.add(oldNg.exactKey, oldNg);
|
|
501
|
+
exprPath.nodeGroupsAttachedAvailable.add(oldNg.closeKey, oldNg);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
class ValueOp extends WatchOp {
|
|
508
|
+
constructor(value) {
|
|
509
|
+
super();
|
|
510
|
+
this.value = value;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
markNodeGroupsAvailable(exprPath) {}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// We detect such ops but we never need to instantiate this class.
|
|
517
|
+
// class ArraySwapOp extends WatchOp {
|
|
518
|
+
// constructor(array, index1, index2) {
|
|
519
|
+
// super();
|
|
520
|
+
// //#IFDEV
|
|
521
|
+
// assert(Array.isArray(array));
|
|
522
|
+
// //#ENDIF
|
|
523
|
+
// this.array = array;
|
|
524
|
+
// this.index1 = index1;
|
|
525
|
+
// this.index2 = index2;
|
|
526
|
+
// }
|
|
527
|
+
// }
|
|
528
|
+
|
|
529
|
+
class WholeArrayOp extends WatchOp {
|
|
530
|
+
constructor(array, value) {
|
|
531
|
+
super();
|
|
532
|
+
//#IFDEV
|
|
533
|
+
assert(Array.isArray(array));
|
|
534
|
+
//#ENDIF
|
|
535
|
+
this.array = array;
|
|
536
|
+
this.value = value;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
markNodeGroupsAvailable(exprPath) {
|
|
540
|
+
for (let i=0; i<exprPath.nodeGroups.length; i++) {
|
|
541
|
+
let oldNg = exprPath.nodeGroups[i];
|
|
542
|
+
exprPath.nodeGroupsAttachedAvailable.add(oldNg.exactKey, oldNg);
|
|
543
|
+
exprPath.nodeGroupsAttachedAvailable.add(oldNg.closeKey, oldNg);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
package/src/solarite/Globals.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
var Globals = {
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Used by NodeGroup.applyComponentExprs() */
|
|
5
|
-
componentHash: new WeakMap(),
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Store which instances of Solarite have already been added to the DOM.
|
|
9
|
-
* @type {WeakSet<HTMLElement>} */
|
|
10
|
-
connected: new WeakSet(),
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Elements that have been rendered to by r() at least once.
|
|
14
|
-
* This is used by the Solarite class to know when to call onFirstConnect()
|
|
15
|
-
* @type {WeakSet<HTMLElement>} */
|
|
16
|
-
rendered: new WeakSet(),
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Used by watch3 to see which expressions are being accessed.
|
|
20
|
-
* @type {[]}*/
|
|
21
|
-
currentExprPath: null,
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @type {Object<string, Class<Node>>} A map from built-in tag names to the constructors that create them. */
|
|
25
|
-
elementClasses: {},
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Used by ExprPath.applyEventAttrib()
|
|
29
|
-
* @type {WeakMap<Node, Object<eventName:string, [original:function, bound:function, args:*[]]>>} */
|
|
30
|
-
nodeEvents: new WeakMap(),
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Get the RootNodeGroup for an element.
|
|
34
|
-
* @type {WeakMap<HTMLElement, RootNodeGroup>} */
|
|
35
|
-
nodeGroups: new WeakMap(),
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Used by r() path 9. */
|
|
39
|
-
objToEl: new WeakMap(),
|
|
40
|
-
|
|
41
|
-
pendingChildren: [],
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Elements that are currently rendering via the r() function.
|
|
45
|
-
* @type {WeakSet<HTMLElement>} */
|
|
46
|
-
rendering: new WeakSet(),
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Map from array of Html strings to a Shell created from them.
|
|
50
|
-
* @type {WeakMap<string[], Shell>} */
|
|
51
|
-
shells: new WeakMap()
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export default Globals;
|