solarite 0.5.2 → 0.7.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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/dist/Solarite-debug.js +2823 -1856
  3. package/dist/Solarite.js +2779 -1824
  4. package/dist/Solarite.min.js +2 -4
  5. package/package.json +16 -3
  6. package/readme.md +58 -11
  7. package/src/Globals.js +54 -72
  8. package/src/HtmlParser.js +90 -90
  9. package/src/MultiValueMap.js +57 -105
  10. package/src/NodeGroup.js +624 -470
  11. package/src/Path.js +224 -211
  12. package/src/PathToAttribValue.js +401 -261
  13. package/src/PathToAttribs.js +113 -80
  14. package/src/PathToComment.js +7 -7
  15. package/src/PathToComponent.js +183 -188
  16. package/src/PathToEvent.js +76 -64
  17. package/src/PathToKey.js +19 -0
  18. package/src/PathToNodes.js +1053 -566
  19. package/src/RootNodeGroup.js +120 -8
  20. package/src/Shell.js +570 -348
  21. package/src/Solarite.d.ts +134 -113
  22. package/src/Solarite.js +243 -286
  23. package/src/Template.js +195 -274
  24. package/src/Util.js +353 -351
  25. package/src/assert.js +10 -10
  26. package/src/assignAttributes.js +63 -0
  27. package/src/delve.js +55 -43
  28. package/src/h.js +220 -138
  29. package/src/jsx-dev-runtime.d.ts +1 -0
  30. package/src/jsx-dev-runtime.js +5 -0
  31. package/src/jsx-runtime.d.ts +21 -0
  32. package/src/jsx-runtime.js +84 -0
  33. package/src/jsx.js +194 -0
  34. package/src/toEl.js +77 -82
  35. package/dist/udomdiff-license.txt +0 -18
  36. package/src/getArg.js +0 -137
  37. package/src/hash.js +0 -89
  38. package/src/udomdiff.js +0 -176
  39. package/src/unused/FastLookupArray.js +0 -54
  40. package/src/unused/Hashes.js +0 -339
  41. package/src/unused/InUse.test.js +0 -92
  42. package/src/unused/InUseMap.js +0 -98
  43. package/src/unused/LinkedList.js +0 -117
  44. package/src/unused/LinkedList.test.js +0 -115
  45. package/src/unused/Misc.js +0 -13
  46. package/src/unused/Perf.js +0 -47
  47. package/src/unused/TrackedArray.js +0 -54
  48. package/src/unused/WeakArray.js +0 -33
  49. package/src/watch.js +0 -543
package/src/watch.js DELETED
@@ -1,543 +0,0 @@
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
- h(this)`
32
- <watch-example>
33
- ${() => this.name + '!'}
34
-
35
- ${this.items.map(item => h`
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 Paths 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 Paths 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.rootNodeGroups.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 [Path, 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 [Path, 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(Path);
95
- Path.watchFunction.newValue = ops.array;
96
- Path.apply([Path.watchFunction], false);
97
-
98
- //Path.freeNodeGroups();
99
-
100
- if (trackModified)
101
- modified.add(Path.nodeMarker);
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
- Path.watchFunction.newValue = ops.value;
110
- Path.apply([Path.watchFunction], false); // False to not free nodeGroups.
111
-
112
- //Path.freeNodeGroups();
113
-
114
- if (trackModified)
115
- modified.add(...Path.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 = Path.nodeGroups[op.index];
134
- let ngb = Path.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
- Path.nodeGroups[op.index] = ngb;
160
- Path.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
- ...Path.nodeGroups.slice(op.index, op.index + op.deleteCount).map(ng => ng.getNodes()).flat()
175
- );
176
-
177
- op.markNodeGroupsAvailable(Path);
178
- Path.applyWatchArrayOp(op);
179
-
180
- if (trackModified && op.items.length) {
181
- Path.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<Path> Paths that will need to be re-rendered when this variable is modified. */
217
- paths = new Set();
218
-
219
- /**
220
- * Paths 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<Path>>} */
223
- childPaths = {};
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 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
- * @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
- const result = (obj === receiver)
265
- ? this.value // top-level value.
266
- : Reflect.get(obj, prop, receiver); // avoid infinite recursion.
267
-
268
- // We override the map() function the first time render() is called.
269
- // But it's not re-overridden when we call renderWatched()
270
- if (Array.isArray(obj)) {
271
-
272
- if (prop === 'map') {
273
-
274
- const self = this;
275
-
276
- // This outer function is so the Path calls it as a function,
277
- // instead of it being evaluated immediately when the Template is created.
278
- // This allows Path.apply() to set the Globals.currentPath before evaluating further.
279
- return (callback) =>
280
-
281
- // This is the new map function.
282
- function mapFunction() {
283
-
284
- // Save the Paths that called the array used by .map()
285
- const currPath = Globals.currentPath;
286
- if (currPath)
287
- self.paths.add(currPath);
288
-
289
- // Apply the map function.
290
- const newObj = mapFunction.newValue || obj;
291
- Globals.currentPath.mapCallback = callback;
292
- // If new Proxy fails b/c newObj isn't an object, make sure the expression is a function.
293
- // TODO: Find a way to warn about this automatically.
294
- let p = new Proxy(newObj, self);
295
- return Array.prototype.map.call(p, callback);
296
- }
297
- }
298
-
299
- else if (prop === 'push' || prop==='pop' || prop === 'splice') {
300
- const rootNg = Globals.rootNodeGroups.get(this.root);
301
- return new WatchedArray(rootNg, obj, this.paths)[prop];
302
- }
303
- }
304
-
305
-
306
- // Save the Path that's currently accessing this variable.
307
- const currPath = Globals.currentPath;
308
-
309
- // Accessing a sub-property
310
- if (result && typeof result === 'object') {
311
- let [proxiedResult, handler] = this.getProxyandHandler(prop, result); // Clone this handler and append prop to the path.
312
-
313
- if (currPath && prop !== 'constructor')
314
- handler.paths.add(currPath);
315
-
316
- return proxiedResult;
317
- }
318
- else {
319
- if (currPath && prop !== 'constructor') {
320
-
321
- // We can't have Proxies on primitive types,
322
- // So we store the affected expressions in the parent Proxy.
323
- if (!this.childPaths[prop])
324
- this.childPaths[prop] = new Set([currPath]);
325
- else
326
- this.childPaths[prop].add(currPath);
327
- }
328
-
329
- return result;
330
- }
331
- }
332
-
333
- // TODO: Will fail for attribute w/ a value having multiple Paths.
334
- // TODO: This won't update a component's expressions.
335
- set(obj, prop, val, receiver) {
336
-
337
- val = removeProxy(val);
338
-
339
- // 1. Add to the list of Paths to re-render.
340
- if (!this.rootNodeGroup)
341
- this.rootNodeGroup = Globals.rootNodeGroups.get(this.root);
342
- const rootNg = this.rootNodeGroup
343
-
344
- // New: // TODO: Should I instead be checking if the old value of val is a primitive?
345
- let isPrimitive = !val || typeof val !== 'object';
346
- let paths = isPrimitive
347
- ? this.childPaths[prop] || []
348
- : this.getProxyandHandler(prop, val)[1].paths;
349
-
350
- const isArray = Array.isArray(obj);
351
- for (let Path of paths) {
352
-
353
- if (isArray) {
354
- if (Number.isInteger(+prop)) {
355
- const exprsToRender = rootNg.exprsToRender.get(Path);
356
-
357
- // If we're not re-rendering the whole thing.
358
- if (!(exprsToRender instanceof WholeArrayOp))
359
- // TODO: Inline this for performance
360
- Util.mapArrayAdd(rootNg.exprsToRender, Path, new ArraySpliceOp(obj, prop, 1, [val]));
361
- }
362
-
363
- // Reapply the whole expression.
364
- else
365
- rootNg.exprsToRender.set(Path, new WholeArrayOp(val));
366
- }
367
- else
368
- rootNg.exprsToRender.set(Path, new ValueOp(val));
369
- }
370
-
371
- // 2. Set the value.
372
- if (obj === receiver)
373
- this.value = val; // top-level value.
374
- else // Set the value while avoiding infinite recursion.
375
- Reflect.set(obj, prop, val, receiver);
376
-
377
- // Value changed, so reset cached proxy.
378
- if (val && typeof val === 'object')
379
- delete this.proxies[prop];
380
-
381
- return true;
382
- }
383
- }
384
-
385
- /**
386
- * This function markes a property of a web component to be watched for changes.
387
- *
388
- * Here is how watches work:
389
- * 1. When we call watch() it creates properties on the root object that return Proxies to watch when values are set.
390
- * 2. When they are set, we add their paths to the rootNodeGroup.exprsToRender that keeps track of what to re-render.
391
- * 3. Then we call renderWatched() to re-render only those parts.
392
- *
393
- * In more detail:
394
- * TODO
395
- *
396
- *
397
- * @param root {HTMLElement} An instance of a Web Component that uses h() to render its content.
398
- * @param field {string} The name of a top-level property of root.
399
- * @param value {string|Symbol} The default value. */
400
- export default function watch(root, field, value=unusedArg) {
401
- // Store internal value used by get/set.
402
- if (value !== unusedArg)
403
- root[field] = value;
404
- else
405
- value = root[field];
406
-
407
- let handler = new ProxyHandler(root, value);
408
- Object.defineProperty(root, field, {
409
- get: () => handler.get(root, field, root),
410
- set: (val) => handler.set(root, field, val, root)
411
- });
412
- }
413
- export {watch};
414
-
415
-
416
- /**
417
- * Wrap an array so that functions that modify the array are intercepted.
418
- * We then add ArraySpliceOp's to the list of ops to run for each affected Path.
419
- * When renderWatched() is called it then applies those ops to the NodeGroups created by the map() function. */
420
- class WatchedArray {
421
-
422
- /**
423
- * @param array {Array}
424
- * @param rootNg {RootNodeGroup}
425
- * @param paths {Path[]|Set<Path>} Expression paths that use this array. */
426
- constructor(rootNg, array, paths) {
427
- this.rootNg = rootNg;
428
- //#IFDEV
429
- assert(Array.isArray(array));
430
- //#ENDIF
431
- this.array = array;
432
- this.paths = paths;
433
- this.push = this.push.bind(this);
434
- this.pop = this.pop.bind(this);
435
- this.splice = this.splice.bind(this);
436
- }
437
-
438
- push(...args) {
439
- return this.internalSplice('push', args, [this.array, this.array.length, 0, args]);
440
- }
441
-
442
- pop() {
443
- if (this.array.length)
444
- return this.internalSplice('pop', [], [this.array, this.array.length-1, 1]);
445
- }
446
-
447
- splice(...args) {
448
- return this.internalSplice('splice', args, [this.array, ...args]);
449
- }
450
-
451
- internalSplice(func, args, spliceArgs) {
452
- // Mark all expressions affected by the array function to be re-rendered
453
- for (let Path of this.paths) {
454
- let exprsToRender = this.rootNg.exprsToRender.get(Path);
455
- if (!(exprsToRender instanceof WholeArrayOp)) // If we're not already going to re-render the whole array.
456
- Util.mapArrayAdd(this.rootNg.exprsToRender, Path, new ArraySpliceOp(...spliceArgs));
457
- }
458
-
459
- // Call original array function
460
- return Array.prototype[func].call(this.array, ...args);
461
- }
462
- }
463
-
464
-
465
-
466
- class WatchOp {}
467
-
468
- export class ArraySpliceOp extends WatchOp {
469
-
470
- /**
471
- * Represents a splice operation (insertion, deletion, or replacement of elements)
472
- * to be applied to an array during rendering.
473
- *
474
- * @param array {Array} The array affected by the splice operation.
475
- * @param index {int} The starting index of the splice operation.
476
- * @param deleteCount {int} The number of elements to delete from the array.
477
- * @param items {Array} The elements to insert into the array at the starting index. */
478
- constructor(array, index, deleteCount, items=[]) {
479
- super();
480
- //#IFDEV
481
- assert(Array.isArray(array));
482
- //#ENDIF
483
- this.array = array;
484
- this.index = index*1;
485
- this.deleteCount = deleteCount;
486
- this.items = items;
487
-
488
- // Save the first item deleted so we can see if this should be turned into an ArraySwapOp later.
489
- this.firstDeleted = deleteCount===1 ? array[index] : undefined;
490
- }
491
-
492
- markNodeGroupsAvailable(Path) {
493
- if (this.deleteCount > 0) {
494
- let count = this.index+this.deleteCount;
495
- for (let i=this.index; i<count; i++) {
496
- let oldNg = Path.nodeGroups[i];
497
- Path.nodeGroupsAttachedAvailable.add(oldNg.exactKey, oldNg);
498
- Path.nodeGroupsAttachedAvailable.add(oldNg.closeKey, oldNg);
499
- }
500
- }
501
- }
502
- }
503
-
504
- class ValueOp extends WatchOp {
505
- constructor(value) {
506
- super();
507
- this.value = value;
508
- }
509
-
510
- markNodeGroupsAvailable(Path) {}
511
- }
512
-
513
- // We detect such ops but we never need to instantiate this class.
514
- // class ArraySwapOp extends WatchOp {
515
- // constructor(array, index1, index2) {
516
- // super();
517
- // //#IFDEV
518
- // assert(Array.isArray(array));
519
- // //#ENDIF
520
- // this.array = array;
521
- // this.index1 = index1;
522
- // this.index2 = index2;
523
- // }
524
- // }
525
-
526
- class WholeArrayOp extends WatchOp {
527
- constructor(array, value) {
528
- super();
529
- //#IFDEV
530
- assert(Array.isArray(array));
531
- //#ENDIF
532
- this.array = array;
533
- this.value = value;
534
- }
535
-
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);
541
- }
542
- }
543
- }