solarite 0.3.2 → 0.5.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/dist/Solarite-debug.js +2149 -2090
- package/dist/Solarite.js +1895 -1821
- package/dist/Solarite.min.js +2 -2
- package/package.json +12 -2
- package/readme.md +2 -2
- package/src/Globals.js +18 -24
- package/src/HtmlParser.js +1 -1
- package/src/MultiValueMap.js +3 -3
- package/src/NodeGroup.js +238 -483
- package/src/Path.js +212 -0
- package/src/PathToAttribValue.js +259 -0
- package/src/PathToAttribs.js +77 -0
- package/src/PathToComment.js +8 -0
- package/src/PathToComponent.js +169 -0
- package/src/PathToEvent.js +57 -0
- package/src/PathToNodes.js +566 -0
- package/src/RootNodeGroup.js +8 -0
- package/src/Shell.js +92 -83
- package/src/Solarite.d.ts +73 -29
- package/src/Solarite.js +133 -21
- package/src/Template.js +173 -42
- package/src/Util.js +74 -52
- package/src/assert.js +5 -5
- package/src/getArg.js +26 -24
- package/src/h.js +83 -128
- package/src/hash.js +11 -9
- package/src/toEl.js +83 -0
- package/src/udomdiff.js +0 -57
- package/src/watch.js +78 -81
- package/src/ExprPath.js +0 -1109
- package/src/createSolarite.js +0 -154
package/src/watch.js
CHANGED
|
@@ -28,11 +28,11 @@ class WatchExample extends Solarite {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
render() {
|
|
31
|
-
|
|
31
|
+
h(this)`
|
|
32
32
|
<watch-example>
|
|
33
33
|
${() => this.name + '!'}
|
|
34
34
|
|
|
35
|
-
${this.items.map(item =>
|
|
35
|
+
${this.items.map(item => h`
|
|
36
36
|
<div>${item.name}</div>
|
|
37
37
|
`)}
|
|
38
38
|
${() => this.items.length}
|
|
@@ -44,7 +44,7 @@ customElements.define('watch-example', WatchExample);
|
|
|
44
44
|
let a = new WatchExample();
|
|
45
45
|
|
|
46
46
|
// Items is a Proxy.
|
|
47
|
-
// Calling push() will trigger the map'd
|
|
47
|
+
// Calling push() will trigger the map'd Paths to add another at the end.
|
|
48
48
|
// And the .length expression to update.
|
|
49
49
|
// Because accessing .items returns a proxy.
|
|
50
50
|
a.items.push({name: 'Fred'});
|
|
@@ -52,7 +52,7 @@ a.items.push({name: 'Fred'});
|
|
|
52
52
|
|
|
53
53
|
import Globals from "./Globals.js";
|
|
54
54
|
import Util from "./Util.js";
|
|
55
|
-
import
|
|
55
|
+
import assert from "./assert.js";
|
|
56
56
|
|
|
57
57
|
let unusedArg = Symbol('unusedArg');
|
|
58
58
|
|
|
@@ -65,12 +65,12 @@ function removeProxy(obj) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* Render the
|
|
68
|
+
* Render the Paths that were added to rootNg.exprsToRender.
|
|
69
69
|
* @param root {HTMLElement}
|
|
70
70
|
* @param trackModified {boolean}
|
|
71
71
|
* @returns {Node[]} Modified elements. */
|
|
72
72
|
export function renderWatched(root, trackModified=false) {
|
|
73
|
-
let rootNg = Globals.
|
|
73
|
+
let rootNg = Globals.rootNodeGroups.get(root);
|
|
74
74
|
let modified;
|
|
75
75
|
|
|
76
76
|
if (trackModified)
|
|
@@ -78,27 +78,27 @@ export function renderWatched(root, trackModified=false) {
|
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
// Mark NodeGroups of expressionpaths as freed.
|
|
81
|
-
// for (let [
|
|
81
|
+
// for (let [Path, ops] of rootNg.exprsToRender) {
|
|
82
82
|
// if (ops instanceof WholeArrayOp) {}
|
|
83
83
|
// else if (ops instanceof ValueOp) {}
|
|
84
84
|
// else {} // Array Slice Op
|
|
85
85
|
// }
|
|
86
86
|
|
|
87
|
-
for (let [
|
|
87
|
+
for (let [Path, ops] of rootNg.exprsToRender) {
|
|
88
88
|
|
|
89
89
|
// Reapply the whole expression.
|
|
90
90
|
if (ops instanceof WholeArrayOp) {
|
|
91
91
|
|
|
92
92
|
// So it doesn't use the old value inside the map callback in the get handler above.
|
|
93
93
|
// TODO: Find a more sensible way to pass newValue.
|
|
94
|
-
ops.markNodeGroupsAvailable(
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
ops.markNodeGroupsAvailable(Path);
|
|
95
|
+
Path.watchFunction.newValue = ops.array;
|
|
96
|
+
Path.apply([Path.watchFunction], false);
|
|
97
97
|
|
|
98
|
-
//
|
|
98
|
+
//Path.freeNodeGroups();
|
|
99
99
|
|
|
100
100
|
if (trackModified)
|
|
101
|
-
modified.add(
|
|
101
|
+
modified.add(Path.nodeMarker);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// Update a single value in a map callback
|
|
@@ -106,13 +106,13 @@ export function renderWatched(root, trackModified=false) {
|
|
|
106
106
|
else if (ops instanceof ValueOp) {
|
|
107
107
|
|
|
108
108
|
// TODO: I need to only free node groups of watched expressions.
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
Path.watchFunction.newValue = ops.value;
|
|
110
|
+
Path.apply([Path.watchFunction], false); // False to not free nodeGroups.
|
|
111
111
|
|
|
112
|
-
//
|
|
112
|
+
//Path.freeNodeGroups();
|
|
113
113
|
|
|
114
114
|
if (trackModified)
|
|
115
|
-
modified.add(...
|
|
115
|
+
modified.add(...Path.getNodes());
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// Selectively update NodeGroups created by array.map()
|
|
@@ -130,8 +130,8 @@ export function renderWatched(root, trackModified=false) {
|
|
|
130
130
|
&& op.array[op.index] === nextOp.firstDeleted
|
|
131
131
|
) {
|
|
132
132
|
|
|
133
|
-
let nga =
|
|
134
|
-
let ngb =
|
|
133
|
+
let nga = Path.nodeGroups[op.index];
|
|
134
|
+
let ngb = Path.nodeGroups[nextOp.index];
|
|
135
135
|
|
|
136
136
|
// Swap the nodegroup nga and ngb node positions
|
|
137
137
|
let nextA = nga.endNode.nextSibling;
|
|
@@ -156,8 +156,8 @@ export function renderWatched(root, trackModified=false) {
|
|
|
156
156
|
nextB.parentNode.insertBefore(node, nextB);
|
|
157
157
|
*/
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
Path.nodeGroups[op.index] = ngb;
|
|
160
|
+
Path.nodeGroups[nextOp.index] = nga;
|
|
161
161
|
|
|
162
162
|
if (trackModified) {
|
|
163
163
|
nga.getNodes().map(n => modified.add(n));
|
|
@@ -171,14 +171,14 @@ export function renderWatched(root, trackModified=false) {
|
|
|
171
171
|
|
|
172
172
|
if (trackModified && op.deleteCount)
|
|
173
173
|
modified.add(
|
|
174
|
-
...
|
|
174
|
+
...Path.nodeGroups.slice(op.index, op.index + op.deleteCount).map(ng => ng.getNodes()).flat()
|
|
175
175
|
);
|
|
176
176
|
|
|
177
|
-
op.markNodeGroupsAvailable(
|
|
178
|
-
|
|
177
|
+
op.markNodeGroupsAvailable(Path);
|
|
178
|
+
Path.applyWatchArrayOp(op);
|
|
179
179
|
|
|
180
180
|
if (trackModified && op.items.length) {
|
|
181
|
-
|
|
181
|
+
Path.nodeGroups.slice(op.index, op.index + op.items.length)
|
|
182
182
|
.map(ng => ng.getNodes())
|
|
183
183
|
.flat()
|
|
184
184
|
.map(n => modified.add(n));
|
|
@@ -213,14 +213,14 @@ class ProxyHandler {
|
|
|
213
213
|
/** @type {Record<string, [Proxy, ProxyHandler]>} Proxies for child properties. */
|
|
214
214
|
proxies = {}
|
|
215
215
|
|
|
216
|
-
/** @type Set<
|
|
217
|
-
|
|
216
|
+
/** @type Set<Path> Paths that will need to be re-rendered when this variable is modified. */
|
|
217
|
+
paths = new Set();
|
|
218
218
|
|
|
219
219
|
/**
|
|
220
|
-
*
|
|
220
|
+
* Paths that will need to be re-rendered when one of this variable's primitive properties is modified,
|
|
221
221
|
* since primitives can't have their own ProxyHandler.
|
|
222
|
-
* @type {Record<prop:string, affected:Set<
|
|
223
|
-
|
|
222
|
+
* @type {Record<prop:string, affected:Set<Path>>} */
|
|
223
|
+
childPaths = {};
|
|
224
224
|
|
|
225
225
|
|
|
226
226
|
constructor(root, value) {
|
|
@@ -250,8 +250,8 @@ class ProxyHandler {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
/**
|
|
253
|
-
* We override get() so we can mark which
|
|
254
|
-
* Then later when we call set on a variable, we can see which
|
|
253
|
+
* We override get() so we can mark which Paths read from each variable in the hierarchy.
|
|
254
|
+
* Then later when we call set on a variable, we can see which Paths use it, and can mark them to be re-rendered.
|
|
255
255
|
* @param obj
|
|
256
256
|
* @param prop {string}
|
|
257
257
|
* @param receiver
|
|
@@ -261,9 +261,6 @@ class ProxyHandler {
|
|
|
261
261
|
if (prop === '$removeProxy')
|
|
262
262
|
return obj;
|
|
263
263
|
|
|
264
|
-
// if (prop === 'items')
|
|
265
|
-
// debugger;
|
|
266
|
-
|
|
267
264
|
const result = (obj === receiver)
|
|
268
265
|
? this.value // top-level value.
|
|
269
266
|
: Reflect.get(obj, prop, receiver); // avoid infinite recursion.
|
|
@@ -276,22 +273,22 @@ class ProxyHandler {
|
|
|
276
273
|
|
|
277
274
|
const self = this;
|
|
278
275
|
|
|
279
|
-
// This outer function is so the
|
|
276
|
+
// This outer function is so the Path calls it as a function,
|
|
280
277
|
// instead of it being evaluated immediately when the Template is created.
|
|
281
|
-
// This allows
|
|
278
|
+
// This allows Path.apply() to set the Globals.currentPath before evaluating further.
|
|
282
279
|
return (callback) =>
|
|
283
280
|
|
|
284
281
|
// This is the new map function.
|
|
285
282
|
function mapFunction() {
|
|
286
283
|
|
|
287
|
-
// Save the
|
|
288
|
-
const
|
|
289
|
-
if (
|
|
290
|
-
self.
|
|
284
|
+
// Save the Paths that called the array used by .map()
|
|
285
|
+
const currPath = Globals.currentPath;
|
|
286
|
+
if (currPath)
|
|
287
|
+
self.paths.add(currPath);
|
|
291
288
|
|
|
292
289
|
// Apply the map function.
|
|
293
290
|
const newObj = mapFunction.newValue || obj;
|
|
294
|
-
Globals.
|
|
291
|
+
Globals.currentPath.mapCallback = callback;
|
|
295
292
|
// If new Proxy fails b/c newObj isn't an object, make sure the expression is a function.
|
|
296
293
|
// TODO: Find a way to warn about this automatically.
|
|
297
294
|
let p = new Proxy(newObj, self);
|
|
@@ -300,75 +297,75 @@ class ProxyHandler {
|
|
|
300
297
|
}
|
|
301
298
|
|
|
302
299
|
else if (prop === 'push' || prop==='pop' || prop === 'splice') {
|
|
303
|
-
const rootNg = Globals.
|
|
304
|
-
return new WatchedArray(rootNg, obj, this.
|
|
300
|
+
const rootNg = Globals.rootNodeGroups.get(this.root);
|
|
301
|
+
return new WatchedArray(rootNg, obj, this.paths)[prop];
|
|
305
302
|
}
|
|
306
303
|
}
|
|
307
304
|
|
|
308
305
|
|
|
309
|
-
// Save the
|
|
310
|
-
const
|
|
306
|
+
// Save the Path that's currently accessing this variable.
|
|
307
|
+
const currPath = Globals.currentPath;
|
|
311
308
|
|
|
312
309
|
// Accessing a sub-property
|
|
313
310
|
if (result && typeof result === 'object') {
|
|
314
311
|
let [proxiedResult, handler] = this.getProxyandHandler(prop, result); // Clone this handler and append prop to the path.
|
|
315
312
|
|
|
316
|
-
if (
|
|
317
|
-
handler.
|
|
313
|
+
if (currPath && prop !== 'constructor')
|
|
314
|
+
handler.paths.add(currPath);
|
|
318
315
|
|
|
319
316
|
return proxiedResult;
|
|
320
317
|
}
|
|
321
318
|
else {
|
|
322
|
-
if (
|
|
319
|
+
if (currPath && prop !== 'constructor') {
|
|
323
320
|
|
|
324
321
|
// We can't have Proxies on primitive types,
|
|
325
322
|
// So we store the affected expressions in the parent Proxy.
|
|
326
|
-
if (!this.
|
|
327
|
-
this.
|
|
323
|
+
if (!this.childPaths[prop])
|
|
324
|
+
this.childPaths[prop] = new Set([currPath]);
|
|
328
325
|
else
|
|
329
|
-
this.
|
|
326
|
+
this.childPaths[prop].add(currPath);
|
|
330
327
|
}
|
|
331
328
|
|
|
332
329
|
return result;
|
|
333
330
|
}
|
|
334
331
|
}
|
|
335
332
|
|
|
336
|
-
// TODO: Will fail for attribute w/ a value having multiple
|
|
333
|
+
// TODO: Will fail for attribute w/ a value having multiple Paths.
|
|
337
334
|
// TODO: This won't update a component's expressions.
|
|
338
335
|
set(obj, prop, val, receiver) {
|
|
339
336
|
|
|
340
337
|
val = removeProxy(val);
|
|
341
338
|
|
|
342
|
-
// 1. Add to the list of
|
|
339
|
+
// 1. Add to the list of Paths to re-render.
|
|
343
340
|
if (!this.rootNodeGroup)
|
|
344
|
-
this.rootNodeGroup = Globals.
|
|
341
|
+
this.rootNodeGroup = Globals.rootNodeGroups.get(this.root);
|
|
345
342
|
const rootNg = this.rootNodeGroup
|
|
346
343
|
|
|
347
344
|
// New: // TODO: Should I instead be checking if the old value of val is a primitive?
|
|
348
345
|
let isPrimitive = !val || typeof val !== 'object';
|
|
349
|
-
let
|
|
350
|
-
? this.
|
|
351
|
-
: this.getProxyandHandler(prop, val)[1].
|
|
346
|
+
let paths = isPrimitive
|
|
347
|
+
? this.childPaths[prop] || []
|
|
348
|
+
: this.getProxyandHandler(prop, val)[1].paths;
|
|
352
349
|
|
|
353
350
|
const isArray = Array.isArray(obj);
|
|
354
|
-
for (let
|
|
351
|
+
for (let Path of paths) {
|
|
355
352
|
|
|
356
353
|
if (isArray) {
|
|
357
354
|
if (Number.isInteger(+prop)) {
|
|
358
|
-
const exprsToRender = rootNg.exprsToRender.get(
|
|
355
|
+
const exprsToRender = rootNg.exprsToRender.get(Path);
|
|
359
356
|
|
|
360
357
|
// If we're not re-rendering the whole thing.
|
|
361
358
|
if (!(exprsToRender instanceof WholeArrayOp))
|
|
362
359
|
// TODO: Inline this for performance
|
|
363
|
-
Util.mapArrayAdd(rootNg.exprsToRender,
|
|
360
|
+
Util.mapArrayAdd(rootNg.exprsToRender, Path, new ArraySpliceOp(obj, prop, 1, [val]));
|
|
364
361
|
}
|
|
365
362
|
|
|
366
363
|
// Reapply the whole expression.
|
|
367
364
|
else
|
|
368
|
-
rootNg.exprsToRender.set(
|
|
365
|
+
rootNg.exprsToRender.set(Path, new WholeArrayOp(val));
|
|
369
366
|
}
|
|
370
367
|
else
|
|
371
|
-
rootNg.exprsToRender.set(
|
|
368
|
+
rootNg.exprsToRender.set(Path, new ValueOp(val));
|
|
372
369
|
}
|
|
373
370
|
|
|
374
371
|
// 2. Set the value.
|
|
@@ -397,7 +394,7 @@ class ProxyHandler {
|
|
|
397
394
|
* TODO
|
|
398
395
|
*
|
|
399
396
|
*
|
|
400
|
-
* @param root {HTMLElement} An instance of a Web Component that uses
|
|
397
|
+
* @param root {HTMLElement} An instance of a Web Component that uses h() to render its content.
|
|
401
398
|
* @param field {string} The name of a top-level property of root.
|
|
402
399
|
* @param value {string|Symbol} The default value. */
|
|
403
400
|
export default function watch(root, field, value=unusedArg) {
|
|
@@ -418,21 +415,21 @@ export {watch};
|
|
|
418
415
|
|
|
419
416
|
/**
|
|
420
417
|
* 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
|
|
418
|
+
* We then add ArraySpliceOp's to the list of ops to run for each affected Path.
|
|
422
419
|
* When renderWatched() is called it then applies those ops to the NodeGroups created by the map() function. */
|
|
423
420
|
class WatchedArray {
|
|
424
421
|
|
|
425
422
|
/**
|
|
426
423
|
* @param array {Array}
|
|
427
424
|
* @param rootNg {RootNodeGroup}
|
|
428
|
-
* @param
|
|
429
|
-
constructor(rootNg, array,
|
|
425
|
+
* @param paths {Path[]|Set<Path>} Expression paths that use this array. */
|
|
426
|
+
constructor(rootNg, array, paths) {
|
|
430
427
|
this.rootNg = rootNg;
|
|
431
428
|
//#IFDEV
|
|
432
429
|
assert(Array.isArray(array));
|
|
433
430
|
//#ENDIF
|
|
434
431
|
this.array = array;
|
|
435
|
-
this.
|
|
432
|
+
this.paths = paths;
|
|
436
433
|
this.push = this.push.bind(this);
|
|
437
434
|
this.pop = this.pop.bind(this);
|
|
438
435
|
this.splice = this.splice.bind(this);
|
|
@@ -453,10 +450,10 @@ class WatchedArray {
|
|
|
453
450
|
|
|
454
451
|
internalSplice(func, args, spliceArgs) {
|
|
455
452
|
// Mark all expressions affected by the array function to be re-rendered
|
|
456
|
-
for (let
|
|
457
|
-
let exprsToRender = this.rootNg.exprsToRender.get(
|
|
453
|
+
for (let Path of this.paths) {
|
|
454
|
+
let exprsToRender = this.rootNg.exprsToRender.get(Path);
|
|
458
455
|
if (!(exprsToRender instanceof WholeArrayOp)) // If we're not already going to re-render the whole array.
|
|
459
|
-
Util.mapArrayAdd(this.rootNg.exprsToRender,
|
|
456
|
+
Util.mapArrayAdd(this.rootNg.exprsToRender, Path, new ArraySpliceOp(...spliceArgs));
|
|
460
457
|
}
|
|
461
458
|
|
|
462
459
|
// Call original array function
|
|
@@ -492,13 +489,13 @@ export class ArraySpliceOp extends WatchOp {
|
|
|
492
489
|
this.firstDeleted = deleteCount===1 ? array[index] : undefined;
|
|
493
490
|
}
|
|
494
491
|
|
|
495
|
-
markNodeGroupsAvailable(
|
|
492
|
+
markNodeGroupsAvailable(Path) {
|
|
496
493
|
if (this.deleteCount > 0) {
|
|
497
494
|
let count = this.index+this.deleteCount;
|
|
498
495
|
for (let i=this.index; i<count; i++) {
|
|
499
|
-
let oldNg =
|
|
500
|
-
|
|
501
|
-
|
|
496
|
+
let oldNg = Path.nodeGroups[i];
|
|
497
|
+
Path.nodeGroupsAttachedAvailable.add(oldNg.exactKey, oldNg);
|
|
498
|
+
Path.nodeGroupsAttachedAvailable.add(oldNg.closeKey, oldNg);
|
|
502
499
|
}
|
|
503
500
|
}
|
|
504
501
|
}
|
|
@@ -510,7 +507,7 @@ class ValueOp extends WatchOp {
|
|
|
510
507
|
this.value = value;
|
|
511
508
|
}
|
|
512
509
|
|
|
513
|
-
markNodeGroupsAvailable(
|
|
510
|
+
markNodeGroupsAvailable(Path) {}
|
|
514
511
|
}
|
|
515
512
|
|
|
516
513
|
// We detect such ops but we never need to instantiate this class.
|
|
@@ -536,11 +533,11 @@ class WholeArrayOp extends WatchOp {
|
|
|
536
533
|
this.value = value;
|
|
537
534
|
}
|
|
538
535
|
|
|
539
|
-
markNodeGroupsAvailable(
|
|
540
|
-
for (let i=0; i<
|
|
541
|
-
let oldNg =
|
|
542
|
-
|
|
543
|
-
|
|
536
|
+
markNodeGroupsAvailable(Path) {
|
|
537
|
+
for (let i=0; i<Path.nodeGroups.length; i++) {
|
|
538
|
+
let oldNg = Path.nodeGroups[i];
|
|
539
|
+
Path.nodeGroupsAttachedAvailable.add(oldNg.exactKey, oldNg);
|
|
540
|
+
Path.nodeGroupsAttachedAvailable.add(oldNg.closeKey, oldNg);
|
|
544
541
|
}
|
|
545
542
|
}
|
|
546
543
|
}
|