aberdeen 1.1.0 → 1.3.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/README.md +32 -15
- package/README.md.bak +212 -0
- package/dist/aberdeen.d.ts +64 -30
- package/dist/aberdeen.js +167 -137
- package/dist/aberdeen.js.map +3 -3
- package/dist/prediction.js.map +1 -1
- package/dist/route.d.ts +3 -2
- package/dist/route.js +4 -4
- package/dist/route.js.map +3 -3
- package/dist-min/aberdeen.js +5 -5
- package/dist-min/aberdeen.js.map +3 -3
- package/dist-min/dispatcher.js +2 -2
- package/dist-min/dispatcher.js.map +2 -2
- package/dist-min/prediction.js +2 -2
- package/dist-min/prediction.js.map +2 -2
- package/dist-min/route.js +2 -2
- package/dist-min/route.js.map +3 -3
- package/html-to-aberdeen +397 -0
- package/package.json +5 -5
- package/src/aberdeen.ts +255 -183
- package/src/route.ts +5 -4
package/src/aberdeen.ts
CHANGED
|
@@ -138,7 +138,7 @@ function partToStr(part: number | string): string {
|
|
|
138
138
|
* ]);
|
|
139
139
|
*
|
|
140
140
|
* onEach(users, (user) => {
|
|
141
|
-
* $(`p
|
|
141
|
+
* $(`p#${user.name}: ${user.score}`);
|
|
142
142
|
* }, (user) => invertString(user.name)); // Reverse alphabetic order
|
|
143
143
|
* ```
|
|
144
144
|
*
|
|
@@ -166,7 +166,9 @@ abstract class Scope implements QueueRunner {
|
|
|
166
166
|
|
|
167
167
|
[ptr: ReverseSortedSetPointer]: this;
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
onChange(index: any): void {
|
|
170
|
+
queue(this);
|
|
171
|
+
}
|
|
170
172
|
abstract queueRun(): void;
|
|
171
173
|
|
|
172
174
|
abstract getLastNode(): Node | undefined;
|
|
@@ -196,8 +198,8 @@ abstract class ContentScope extends Scope {
|
|
|
196
198
|
// be for child scopes, subscriptions as well as `clean(..)` hooks.
|
|
197
199
|
cleaners: Array<{ delete: (scope: Scope) => void } | (() => void)>;
|
|
198
200
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
+
abstract svg: boolean;
|
|
202
|
+
abstract el: Element;
|
|
201
203
|
|
|
202
204
|
constructor(
|
|
203
205
|
cleaners: Array<{ delete: (scope: Scope) => void } | (() => void)> = [],
|
|
@@ -211,8 +213,6 @@ abstract class ContentScope extends Scope {
|
|
|
211
213
|
// Should be subclassed in most cases..
|
|
212
214
|
redraw() {}
|
|
213
215
|
|
|
214
|
-
abstract parentElement: Element;
|
|
215
|
-
|
|
216
216
|
getLastNode(): Node | undefined {
|
|
217
217
|
return findLastNodeInPrevSiblings(this.lastChild);
|
|
218
218
|
}
|
|
@@ -260,16 +260,15 @@ class ChainedScope extends ContentScope {
|
|
|
260
260
|
|
|
261
261
|
constructor(
|
|
262
262
|
// The parent DOM element we'll add our child nodes to.
|
|
263
|
-
public
|
|
263
|
+
public el: Element,
|
|
264
|
+
// Whether this scope is within an SVG namespace context
|
|
265
|
+
public svg: boolean,
|
|
264
266
|
// When true, we share our 'cleaners' list with the parent scope.
|
|
265
267
|
useParentCleaners = false,
|
|
266
268
|
) {
|
|
267
269
|
super(useParentCleaners ? currentScope.cleaners : []);
|
|
268
270
|
|
|
269
|
-
|
|
270
|
-
this.inSvgNamespace = currentScope.inSvgNamespace;
|
|
271
|
-
|
|
272
|
-
if (parentElement === currentScope.parentElement) {
|
|
271
|
+
if (el === currentScope.el) {
|
|
273
272
|
// If `currentScope` is not actually a ChainedScope, prevSibling will be undefined, as intended
|
|
274
273
|
this.prevSibling = currentScope.getChildPrevSibling();
|
|
275
274
|
currentScope.lastChild = this;
|
|
@@ -300,12 +299,13 @@ class ChainedScope extends ContentScope {
|
|
|
300
299
|
*/
|
|
301
300
|
class RegularScope extends ChainedScope {
|
|
302
301
|
constructor(
|
|
303
|
-
|
|
302
|
+
el: Element,
|
|
303
|
+
svg: boolean,
|
|
304
304
|
// The function that will be reactively called. Elements it creates using `$` are
|
|
305
305
|
// added to the appropriate position within `parentElement`.
|
|
306
306
|
public renderer: () => any,
|
|
307
307
|
) {
|
|
308
|
-
super(
|
|
308
|
+
super(el, svg);
|
|
309
309
|
|
|
310
310
|
// Do the initial run
|
|
311
311
|
this.redraw();
|
|
@@ -325,23 +325,23 @@ class RegularScope extends ChainedScope {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
class RootScope extends ContentScope {
|
|
328
|
-
|
|
328
|
+
el = document.body;
|
|
329
|
+
svg = false;
|
|
329
330
|
getPrecedingNode(): Node | undefined {
|
|
330
331
|
return undefined;
|
|
331
332
|
}
|
|
332
333
|
}
|
|
333
334
|
|
|
334
335
|
class MountScope extends ContentScope {
|
|
336
|
+
svg: boolean;
|
|
335
337
|
constructor(
|
|
336
338
|
// The parent DOM element we'll add our child nodes to
|
|
337
|
-
public
|
|
339
|
+
public el: Element,
|
|
338
340
|
// The function that
|
|
339
341
|
public renderer: () => any,
|
|
340
342
|
) {
|
|
341
343
|
super();
|
|
342
|
-
|
|
343
|
-
// Inherit SVG namespace state from current scope
|
|
344
|
-
this.inSvgNamespace = currentScope.inSvgNamespace;
|
|
344
|
+
this.svg = el.namespaceURI === 'http://www.w3.org/2000/svg';
|
|
345
345
|
|
|
346
346
|
this.redraw();
|
|
347
347
|
currentScope.cleaners.push(this);
|
|
@@ -408,11 +408,9 @@ class ResultScope<T> extends ChainedScope {
|
|
|
408
408
|
public result: ValueRef<T> = optProxy({ value: undefined });
|
|
409
409
|
|
|
410
410
|
constructor(
|
|
411
|
-
parentElement: Element,
|
|
412
411
|
public renderer: () => T,
|
|
413
412
|
) {
|
|
414
|
-
super(
|
|
415
|
-
|
|
413
|
+
super(currentScope.el, currentScope.svg);
|
|
416
414
|
this.redraw();
|
|
417
415
|
}
|
|
418
416
|
|
|
@@ -435,18 +433,19 @@ class ResultScope<T> extends ChainedScope {
|
|
|
435
433
|
*/
|
|
436
434
|
|
|
437
435
|
class SetArgScope extends ChainedScope {
|
|
436
|
+
public svg = false;
|
|
438
437
|
constructor(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
438
|
+
el: Element,
|
|
439
|
+
private key: string,
|
|
440
|
+
private target: { value: any },
|
|
442
441
|
) {
|
|
443
|
-
super(
|
|
442
|
+
super(el, el.namespaceURI === 'http://www.w3.org/2000/svg');
|
|
444
443
|
this.redraw();
|
|
445
444
|
}
|
|
446
445
|
redraw() {
|
|
447
446
|
const savedScope = currentScope;
|
|
448
447
|
currentScope = this;
|
|
449
|
-
applyArg(this.key, this.target.value);
|
|
448
|
+
applyArg(this.el, this.key, this.target.value);
|
|
450
449
|
currentScope = savedScope;
|
|
451
450
|
}
|
|
452
451
|
}
|
|
@@ -454,7 +453,7 @@ class SetArgScope extends ChainedScope {
|
|
|
454
453
|
/** @internal */
|
|
455
454
|
class OnEachScope extends Scope {
|
|
456
455
|
// biome-ignore lint/correctness/noInvalidUseBeforeDeclaration: circular, as currentScope is initialized with a Scope
|
|
457
|
-
parentElement: Element = currentScope.
|
|
456
|
+
parentElement: Element = currentScope.el;
|
|
458
457
|
prevSibling: Node | Scope | undefined;
|
|
459
458
|
|
|
460
459
|
/** The data structure we are iterating */
|
|
@@ -556,7 +555,8 @@ class OnEachScope extends Scope {
|
|
|
556
555
|
/** @internal */
|
|
557
556
|
class OnEachItemScope extends ContentScope {
|
|
558
557
|
sortKey: string | number | undefined; // When undefined, this scope is currently not showing in the list
|
|
559
|
-
public
|
|
558
|
+
public el: Element;
|
|
559
|
+
public svg: boolean;
|
|
560
560
|
|
|
561
561
|
constructor(
|
|
562
562
|
public parent: OnEachScope,
|
|
@@ -564,10 +564,10 @@ class OnEachItemScope extends ContentScope {
|
|
|
564
564
|
topRedraw: boolean,
|
|
565
565
|
) {
|
|
566
566
|
super();
|
|
567
|
-
this.
|
|
567
|
+
this.el = parent.parentElement;
|
|
568
568
|
|
|
569
569
|
// Inherit SVG namespace state from current scope
|
|
570
|
-
this.
|
|
570
|
+
this.svg = currentScope.svg;
|
|
571
571
|
|
|
572
572
|
this.parent.byIndex.set(this.itemIndex, this);
|
|
573
573
|
|
|
@@ -711,8 +711,12 @@ class OnEachItemScope extends ContentScope {
|
|
|
711
711
|
}
|
|
712
712
|
}
|
|
713
713
|
|
|
714
|
-
function addNode(node: Node) {
|
|
715
|
-
|
|
714
|
+
function addNode(el: Element, node: Node) {
|
|
715
|
+
if (el !== currentScope.el) {
|
|
716
|
+
el.appendChild(node);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const parentEl = currentScope.el;
|
|
716
720
|
const prevNode = currentScope.getInsertAfterNode();
|
|
717
721
|
parentEl.insertBefore(
|
|
718
722
|
node,
|
|
@@ -842,7 +846,7 @@ export function onEach<K extends string | number | symbol, T>(
|
|
|
842
846
|
* const items = proxy(['apple', 'banana', 'cherry']);
|
|
843
847
|
*
|
|
844
848
|
* // Basic iteration
|
|
845
|
-
* onEach(items, (item, index) => $(`li
|
|
849
|
+
* onEach(items, (item, index) => $(`li#${item} (#${index})`));
|
|
846
850
|
*
|
|
847
851
|
* // Add a new item - the list updates automatically
|
|
848
852
|
* setTimeout(() => items.push('durian'), 2000);
|
|
@@ -861,7 +865,7 @@ export function onEach<K extends string | number | symbol, T>(
|
|
|
861
865
|
*
|
|
862
866
|
* // Sort by name alphabetically
|
|
863
867
|
* onEach(users, (user) => {
|
|
864
|
-
* $(`p
|
|
868
|
+
* $(`p#${user.name} (id=${user.id})`);
|
|
865
869
|
* }, (user) => [user.group, user.name]); // Sort by group, and within each group sort by name
|
|
866
870
|
* ```
|
|
867
871
|
*
|
|
@@ -873,8 +877,8 @@ export function onEach<K extends string | number | symbol, T>(
|
|
|
873
877
|
* $('dl', () => {
|
|
874
878
|
* onEach(config, (value, key) => {
|
|
875
879
|
* if (key === 'showTips') return; // Don't render this one
|
|
876
|
-
* $('dt
|
|
877
|
-
* $('dd
|
|
880
|
+
* $('dt#'+key);
|
|
881
|
+
* $('dd#'+value);
|
|
878
882
|
* });
|
|
879
883
|
* });
|
|
880
884
|
*
|
|
@@ -921,9 +925,9 @@ const EMPTY = Symbol("empty");
|
|
|
921
925
|
* // Reactively display a message if the items array is empty
|
|
922
926
|
* $('div', () => {
|
|
923
927
|
* if (isEmpty(items)) {
|
|
924
|
-
* $('p', 'i
|
|
928
|
+
* $('p', 'i#No items yet!');
|
|
925
929
|
* } else {
|
|
926
|
-
* onEach(items, item=>$('p
|
|
930
|
+
* onEach(items, item=>$('p#'+item));
|
|
927
931
|
* }
|
|
928
932
|
* });
|
|
929
933
|
*
|
|
@@ -1056,7 +1060,7 @@ const objectHandler: ProxyHandler<any> = {
|
|
|
1056
1060
|
return true;
|
|
1057
1061
|
},
|
|
1058
1062
|
deleteProperty(target: any, prop: any) {
|
|
1059
|
-
const old = target.hasOwnProperty(prop) ? target[prop] :
|
|
1063
|
+
const old = target.hasOwnProperty(prop) ? target[prop] : EMPTY;
|
|
1060
1064
|
delete target[prop];
|
|
1061
1065
|
emit(target, prop, EMPTY, old);
|
|
1062
1066
|
return true;
|
|
@@ -1268,7 +1272,8 @@ function optProxy(value: any): any {
|
|
|
1268
1272
|
if (
|
|
1269
1273
|
typeof value !== "object" ||
|
|
1270
1274
|
!value ||
|
|
1271
|
-
value[TARGET_SYMBOL] !== undefined
|
|
1275
|
+
value[TARGET_SYMBOL] !== undefined ||
|
|
1276
|
+
NO_COPY in value
|
|
1272
1277
|
) {
|
|
1273
1278
|
return value;
|
|
1274
1279
|
}
|
|
@@ -1289,6 +1294,25 @@ function optProxy(value: any): any {
|
|
|
1289
1294
|
return proxied;
|
|
1290
1295
|
}
|
|
1291
1296
|
|
|
1297
|
+
/**
|
|
1298
|
+
* When `proxy` is called with a Promise, the returned object has this shape.
|
|
1299
|
+
*/
|
|
1300
|
+
export interface PromiseProxy<T> {
|
|
1301
|
+
/**
|
|
1302
|
+
* True if the promise is still pending, false if it has resolved or rejected.
|
|
1303
|
+
*/
|
|
1304
|
+
busy: boolean;
|
|
1305
|
+
/**
|
|
1306
|
+
* If the promise has resolved, this contains the resolved value.
|
|
1307
|
+
*/
|
|
1308
|
+
value?: T;
|
|
1309
|
+
/**
|
|
1310
|
+
* If the promise has rejected, this contains the rejection error.
|
|
1311
|
+
*/
|
|
1312
|
+
error?: any;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
export function proxy<T extends any>(target: Promise<T>): PromiseProxy<T>;
|
|
1292
1316
|
export function proxy<T extends any>(target: Array<T>): Array<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T >;
|
|
1293
1317
|
export function proxy<T extends object>(target: T): T;
|
|
1294
1318
|
export function proxy<T extends any>(target: T): ValueRef<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T>;
|
|
@@ -1304,6 +1328,10 @@ export function proxy<T extends any>(target: T): ValueRef<T extends number ? num
|
|
|
1304
1328
|
* property access and mutations, but otherwise works like the underlying data.
|
|
1305
1329
|
* - Primitives (string, number, boolean, null, undefined) are wrapped in an object
|
|
1306
1330
|
* `{ value: T }` which is then proxied. Access the primitive via the `.value` property.
|
|
1331
|
+
* - Promises are represented by proxied objects `{ busy: boolean, value?: T, error?: any }`.
|
|
1332
|
+
* Initially, `busy` is `true`. When the promise resolves, `value` is set and `busy`
|
|
1333
|
+
* is set to `false`. If the promise is rejected, `error` is set and `busy` is also
|
|
1334
|
+
* set to `false`.
|
|
1307
1335
|
*
|
|
1308
1336
|
* Use {@link unproxy} to get the original underlying data back.
|
|
1309
1337
|
*
|
|
@@ -1347,6 +1375,21 @@ export function proxy<T extends any>(target: T): ValueRef<T extends number ? num
|
|
|
1347
1375
|
* ```
|
|
1348
1376
|
*/
|
|
1349
1377
|
export function proxy(target: TargetType): TargetType {
|
|
1378
|
+
if (target instanceof Promise) {
|
|
1379
|
+
const result: PromiseProxy<any> = optProxy({
|
|
1380
|
+
busy: true,
|
|
1381
|
+
});
|
|
1382
|
+
target
|
|
1383
|
+
.then((value) => {
|
|
1384
|
+
result.value = value;
|
|
1385
|
+
result.busy = false;
|
|
1386
|
+
})
|
|
1387
|
+
.catch((err) => {
|
|
1388
|
+
result.error = err;
|
|
1389
|
+
result.busy = false;
|
|
1390
|
+
});
|
|
1391
|
+
return result;
|
|
1392
|
+
}
|
|
1350
1393
|
return optProxy(
|
|
1351
1394
|
typeof target === "object" && target !== null ? target : { value: target },
|
|
1352
1395
|
);
|
|
@@ -1439,14 +1482,14 @@ export function copy<T extends object>(dst: T, src: T): boolean;
|
|
|
1439
1482
|
export function copy<T extends object>(dst: T, dstKey: keyof T, src: T[typeof dstKey]): boolean;
|
|
1440
1483
|
export function copy(a: any, b: any, c?: any): boolean {
|
|
1441
1484
|
if (arguments.length > 2) return copySet(a, b, c, 0);
|
|
1442
|
-
return
|
|
1485
|
+
return copyImpl(a, b, 0);
|
|
1443
1486
|
}
|
|
1444
1487
|
|
|
1445
1488
|
function copySet(dst: any, dstKey: any, src: any, flags: number): boolean {
|
|
1446
1489
|
let dstVal = peek(dst, dstKey);
|
|
1447
1490
|
if (src === dstVal) return false;
|
|
1448
1491
|
if (typeof dstVal === "object" && dstVal && typeof src === "object" && src && dstVal.constructor === src.constructor) {
|
|
1449
|
-
return
|
|
1492
|
+
return copyImpl(dstVal, src, flags);
|
|
1450
1493
|
}
|
|
1451
1494
|
src = clone(src);
|
|
1452
1495
|
if (dst instanceof Map) dst.set(dstKey, src);
|
|
@@ -1483,26 +1526,31 @@ export function merge<T extends object>(dst: T, value: Partial<T>): boolean;
|
|
|
1483
1526
|
export function merge<T extends object>(dst: T, dstKey: keyof T, value: Partial<T[typeof dstKey]>): boolean;
|
|
1484
1527
|
export function merge(a: any, b: any, c?: any) {
|
|
1485
1528
|
if (arguments.length > 2) return copySet(a, b, c, MERGE);
|
|
1486
|
-
return
|
|
1529
|
+
return copyImpl(a, b, MERGE);
|
|
1487
1530
|
}
|
|
1488
1531
|
|
|
1489
|
-
|
|
1490
|
-
function copyRecursive<T extends object>(dst: T, src: T, flags: number): boolean {
|
|
1532
|
+
function copyImpl(dst: any, src: any, flags: number): boolean {
|
|
1491
1533
|
// We never want to subscribe to reads we do to the target (to find changes). So we'll
|
|
1492
1534
|
// take the unproxied version and `emit` updates ourselve.
|
|
1493
|
-
let unproxied = (dst as any)[TARGET_SYMBOL]
|
|
1535
|
+
let unproxied = (dst as any)[TARGET_SYMBOL];
|
|
1494
1536
|
if (unproxied) {
|
|
1495
1537
|
dst = unproxied;
|
|
1496
1538
|
flags |= COPY_EMIT;
|
|
1497
1539
|
}
|
|
1498
1540
|
// For performance, we'll work on the unproxied `src` and manually subscribe to changes.
|
|
1499
|
-
unproxied = (src as any)[TARGET_SYMBOL]
|
|
1541
|
+
unproxied = (src as any)[TARGET_SYMBOL];
|
|
1500
1542
|
if (unproxied) {
|
|
1501
1543
|
src = unproxied;
|
|
1502
1544
|
// If we're not in peek mode, we'll manually subscribe to all source reads.
|
|
1503
1545
|
if (currentScope !== ROOT_SCOPE && !peeking) flags |= COPY_SUBSCRIBE;
|
|
1504
1546
|
}
|
|
1505
1547
|
|
|
1548
|
+
return copyRecursive(dst, src, flags);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// The dst and src parameters must be objects. Will throw a friendly message if they're not both the same type.
|
|
1552
|
+
function copyRecursive<T extends object>(dst: T, src: T, flags: number): boolean {
|
|
1553
|
+
|
|
1506
1554
|
if (flags & COPY_SUBSCRIBE) subscribe(src, ANY_SYMBOL);
|
|
1507
1555
|
let changed = false;
|
|
1508
1556
|
|
|
@@ -1525,7 +1573,7 @@ function copyRecursive<T extends object>(dst: T, src: T, flags: number): boolean
|
|
|
1525
1573
|
}
|
|
1526
1574
|
else if (dstValue !== srcValue) {
|
|
1527
1575
|
if (srcValue && typeof srcValue === "object") {
|
|
1528
|
-
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor) {
|
|
1576
|
+
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor && !(NO_COPY in srcValue)) {
|
|
1529
1577
|
changed = copyRecursive(dstValue, srcValue, flags) || changed;
|
|
1530
1578
|
continue;
|
|
1531
1579
|
}
|
|
@@ -1561,7 +1609,7 @@ function copyRecursive<T extends object>(dst: T, src: T, flags: number): boolean
|
|
|
1561
1609
|
if (dstValue === undefined && !dst.has(key)) dstValue = EMPTY;
|
|
1562
1610
|
if (dstValue !== srcValue) {
|
|
1563
1611
|
if (srcValue && typeof srcValue === "object") {
|
|
1564
|
-
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor) {
|
|
1612
|
+
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor && !(NO_COPY in srcValue)) {
|
|
1565
1613
|
changed = copyRecursive(dstValue, srcValue, flags) || changed;
|
|
1566
1614
|
continue;
|
|
1567
1615
|
}
|
|
@@ -1594,7 +1642,7 @@ function copyRecursive<T extends object>(dst: T, src: T, flags: number): boolean
|
|
|
1594
1642
|
const dstValue = dst.hasOwnProperty(key) ? dst[key] : EMPTY;
|
|
1595
1643
|
if (dstValue !== srcValue) {
|
|
1596
1644
|
if (srcValue && typeof srcValue === "object") {
|
|
1597
|
-
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor) {
|
|
1645
|
+
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor && !(NO_COPY in srcValue)) {
|
|
1598
1646
|
changed = copyRecursive(dstValue as typeof srcValue, srcValue, flags) || changed;
|
|
1599
1647
|
continue;
|
|
1600
1648
|
}
|
|
@@ -1630,6 +1678,16 @@ const MERGE = 1;
|
|
|
1630
1678
|
const COPY_SUBSCRIBE = 32;
|
|
1631
1679
|
const COPY_EMIT = 64;
|
|
1632
1680
|
|
|
1681
|
+
/**
|
|
1682
|
+
* A symbol that can be added to an object to prevent it from being cloned by {@link clone} or {@link copy}.
|
|
1683
|
+
* This is useful for objects that should be shared by reference. That also mean that their contents won't
|
|
1684
|
+
* be observed for changes.
|
|
1685
|
+
*/
|
|
1686
|
+
export const NO_COPY = Symbol("NO_COPY");
|
|
1687
|
+
|
|
1688
|
+
// Promises break when proxied, so we'll just mark them as NO_COPY
|
|
1689
|
+
(Promise.prototype as any)[NO_COPY] = true;
|
|
1690
|
+
|
|
1633
1691
|
/**
|
|
1634
1692
|
* Clone an (optionally proxied) object or array.
|
|
1635
1693
|
*
|
|
@@ -1638,11 +1696,12 @@ const COPY_EMIT = 64;
|
|
|
1638
1696
|
* @returns A new unproxied array or object (of the same type as `src`), containing a deep copy of `src`.
|
|
1639
1697
|
*/
|
|
1640
1698
|
export function clone<T extends object>(src: T): T {
|
|
1699
|
+
if (NO_COPY in src) return src;
|
|
1641
1700
|
// Create an empty object of the same type
|
|
1642
1701
|
const copied = Array.isArray(src) ? [] : src instanceof Map ? new Map() : Object.create(Object.getPrototypeOf(src));
|
|
1643
1702
|
// Copy all properties to it. This doesn't need to emit anything, and because
|
|
1644
1703
|
// the destination is an empty object, we can just MERGE, which is a bit faster.
|
|
1645
|
-
|
|
1704
|
+
copyImpl(copied, src, MERGE);
|
|
1646
1705
|
return copied;
|
|
1647
1706
|
}
|
|
1648
1707
|
|
|
@@ -1695,7 +1754,7 @@ const refHandler: ProxyHandler<RefTarget> = {
|
|
|
1695
1754
|
* });
|
|
1696
1755
|
*
|
|
1697
1756
|
* // Usage as a dynamic property, causes a TextNode with just the name to be created and live-updated
|
|
1698
|
-
* $('p
|
|
1757
|
+
* $('p#Selected color: ', {
|
|
1699
1758
|
* text: ref(formData, 'color'),
|
|
1700
1759
|
* $color: ref(formData, 'color')
|
|
1701
1760
|
* });
|
|
@@ -1758,9 +1817,8 @@ function applyBind(el: HTMLInputElement, target: any) {
|
|
|
1758
1817
|
});
|
|
1759
1818
|
}
|
|
1760
1819
|
|
|
1761
|
-
const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
1762
|
-
create: (value: any) => {
|
|
1763
|
-
const el = currentScope.parentElement;
|
|
1820
|
+
const SPECIAL_PROPS: { [key: string]: (el: Element, value: any) => void } = {
|
|
1821
|
+
create: (el: Element, value: any) => {
|
|
1764
1822
|
if (currentScope !== topRedrawScope) return;
|
|
1765
1823
|
if (typeof value === "function") {
|
|
1766
1824
|
value(el);
|
|
@@ -1774,20 +1832,19 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1774
1832
|
})();
|
|
1775
1833
|
}
|
|
1776
1834
|
},
|
|
1777
|
-
destroy: (value: any) => {
|
|
1778
|
-
const el = currentScope.parentElement;
|
|
1835
|
+
destroy: (el: Element, value: any) => {
|
|
1779
1836
|
onDestroyMap.set(el, value);
|
|
1780
1837
|
},
|
|
1781
|
-
html: (value: any) => {
|
|
1838
|
+
html: (el: Element, value: any) => {
|
|
1782
1839
|
const tmpParent = document.createElement(
|
|
1783
|
-
currentScope.
|
|
1840
|
+
currentScope.el.tagName,
|
|
1784
1841
|
);
|
|
1785
1842
|
tmpParent.innerHTML = `${value}`;
|
|
1786
|
-
while (tmpParent.firstChild) addNode(tmpParent.firstChild);
|
|
1843
|
+
while (tmpParent.firstChild) addNode(el, tmpParent.firstChild);
|
|
1844
|
+
},
|
|
1845
|
+
text: (el: Element, value: any) => {
|
|
1846
|
+
addNode(el, document.createTextNode(value));
|
|
1787
1847
|
},
|
|
1788
|
-
text: (value: any) => {
|
|
1789
|
-
addNode(document.createTextNode(value));
|
|
1790
|
-
}
|
|
1791
1848
|
};
|
|
1792
1849
|
|
|
1793
1850
|
/**
|
|
@@ -1798,11 +1855,13 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1798
1855
|
* @param {...(string | function | object | false | undefined | null)} args - Any number of arguments can be given. How they're interpreted depends on their types:
|
|
1799
1856
|
*
|
|
1800
1857
|
* - `string`: Strings can be used to create and insert new elements, set classnames for the *current* element, and add text to the current element.
|
|
1801
|
-
* The format of a string is: **tag
|
|
1802
|
-
*
|
|
1803
|
-
* -
|
|
1804
|
-
* - Any number of CSS classes prefixed by `.` characters. These classes will be added to the *current* element.
|
|
1805
|
-
* -
|
|
1858
|
+
* The format of a string is: (**tag** | `.` **class** | **key**=**val** | **key**="**long val**")* ('#' **text** | **key**=)?
|
|
1859
|
+
* So there can be:
|
|
1860
|
+
* - Any number of **tag** element, like `h1` or `div`. These elements are created, added to the *current* element, and become the new *current* element for the rest of this `$` function execution.
|
|
1861
|
+
* - Any number of CSS classes prefixed by `.` characters. These classes will be added to the *current* element. Optionally, CSS classes can be appended to a **tag** without a space. So both `div.myclass` and `div .myclass` are valid and do the same thing.
|
|
1862
|
+
* - Any number of key/value pairs with string values, like `placeholder="Your name"` or `data-id=123`. These will be handled according to the rules specified for `object`, below, but with the caveat that values can only be strings. The quotes around string values are optional, unless the value contains spaces. It's not possible to escape quotes within the value. If you want to do that, or if you have user-provided values, use the `object` syntax (see below) or end your string with `key=` followed by the data as a separate argument (see below).
|
|
1863
|
+
* - The string may end in a '#' followed by text, which will be added as a TextNode to the *current* element. The text ranges til the end of the string, and may contain any characters, including spaces and quotes.
|
|
1864
|
+
* - Alternatively, the string may end in a key followed by an '=' character, in which case the value is expected as a separate argument. The key/value pair is set according to the rules specified for `object` below. This is useful when the value is not a string or contains spaces or user data. Example: `$('button text="Click me" click=', () => alert('Clicked!'))` or `$('input.value=', someUserData, "placeholder=", "Type your stuff")`.
|
|
1806
1865
|
* - `function`: When a function (without argument nor a return value) is passed in, it will be reactively executed in its own observer scope, preserving the *current element*. So any `$()` invocations within this function will create DOM elements with our *current* element as parent. If the function reads observable data, and that data is changed later on, the function we re-execute (after side effects, such as DOM modifications through `$`, have been cleaned - see also {@link clean}).
|
|
1807
1866
|
* - `object`: When an object is passed in, its key-value pairs are used to modify the *current* element in the following ways...
|
|
1808
1867
|
* - `{<attrName>: any}`: The common case is setting the value as an HTML attribute named key. So `{placeholder: "Your name"}` would add `placeholder="Your name"` to the current HTML element.
|
|
@@ -1817,7 +1876,7 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1817
1876
|
* - `{<any>: <obsvalue>}`: Create a new observer scope and read the `value` property of the given observable (proxy) variable from within it, and apply the contained value using any of the other rules in this list. Example:
|
|
1818
1877
|
* ```typescript
|
|
1819
1878
|
* const myColor = proxy('red');
|
|
1820
|
-
* $('p
|
|
1879
|
+
* $('p#Test', {$color: myColor, click: () => myColor.value = 'yellow'})
|
|
1821
1880
|
* // Clicking the text will cause it to change color without recreating the <p> itself
|
|
1822
1881
|
* ```
|
|
1823
1882
|
* This is often used together with {@link ref}, in order to use properties other than `.value`.
|
|
@@ -1830,16 +1889,24 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1830
1889
|
*
|
|
1831
1890
|
* @example Create Element
|
|
1832
1891
|
* ```typescript
|
|
1833
|
-
* $('button.secondary.outline
|
|
1892
|
+
* $('button.secondary.outline#Submit', {
|
|
1834
1893
|
* disabled: false,
|
|
1835
1894
|
* click: () => console.log('Clicked!'),
|
|
1836
1895
|
* $color: 'red'
|
|
1837
1896
|
* });
|
|
1838
1897
|
* ```
|
|
1898
|
+
*
|
|
1899
|
+
* Which can also be written as:
|
|
1900
|
+
* ```typescript
|
|
1901
|
+
* $('button.secondary.outline text=Submit $color=red disabled=', false, 'click=', () => console.log('Clicked!'));
|
|
1902
|
+
* ```
|
|
1903
|
+
*
|
|
1904
|
+
* We want to set `disabled` as a property instead of an attribute, so we must use the `key=` syntax in order to provide
|
|
1905
|
+
* `false` as a boolean instead of a string.
|
|
1839
1906
|
*
|
|
1840
1907
|
* @example Create Nested Elements
|
|
1841
1908
|
* ```typescript
|
|
1842
|
-
* let inputElement: Element = $('label
|
|
1909
|
+
* let inputElement: Element = $('label#Click me', 'input', {type: 'checkbox'});
|
|
1843
1910
|
* // You should usually not touch raw DOM elements, unless when integrating
|
|
1844
1911
|
* // with non-Aberdeen code.
|
|
1845
1912
|
* console.log('DOM element:', inputElement);
|
|
@@ -1850,8 +1917,8 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1850
1917
|
* const state = proxy({ count: 0 });
|
|
1851
1918
|
* $('div', () => { // Outer element
|
|
1852
1919
|
* // This scope re-renders when state.count changes
|
|
1853
|
-
* $(`p
|
|
1854
|
-
* $('button
|
|
1920
|
+
* $(`p#Count is ${state.count}`);
|
|
1921
|
+
* $('button#Increment', { click: () => state.count++ });
|
|
1855
1922
|
* });
|
|
1856
1923
|
* ```
|
|
1857
1924
|
*
|
|
@@ -1860,17 +1927,17 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1860
1927
|
* const user = proxy({ name: '' });
|
|
1861
1928
|
* $('input', { placeholder: 'Name', bind: ref(user, 'name') });
|
|
1862
1929
|
* $('h3', () => { // Reactive scope
|
|
1863
|
-
* $(
|
|
1930
|
+
* $(`#Hello ${user.name || 'stranger'}`);
|
|
1864
1931
|
* });
|
|
1865
1932
|
* ```
|
|
1866
1933
|
*
|
|
1867
1934
|
* @example Conditional Rendering
|
|
1868
1935
|
* ```typescript
|
|
1869
1936
|
* const show = proxy(false);
|
|
1870
|
-
* $('button', { click: () => show.value = !show.value }, () => $(show.value ? '
|
|
1937
|
+
* $('button', { click: () => show.value = !show.value }, () => $(show.value ? '#Hide' : '#Show'));
|
|
1871
1938
|
* $(() => { // Reactive scope
|
|
1872
1939
|
* if (show.value) {
|
|
1873
|
-
* $('p
|
|
1940
|
+
* $('p#Details are visible!');
|
|
1874
1941
|
* }
|
|
1875
1942
|
* });
|
|
1876
1943
|
* ```
|
|
@@ -1886,96 +1953,104 @@ export function $(
|
|
|
1886
1953
|
| Record<string, any>
|
|
1887
1954
|
)[]
|
|
1888
1955
|
): undefined | Element {
|
|
1889
|
-
let
|
|
1890
|
-
let
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
for
|
|
1894
|
-
|
|
1895
|
-
if (
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1956
|
+
let el: undefined | Element = currentScope.el;
|
|
1957
|
+
let svg: boolean = currentScope.svg
|
|
1958
|
+
|
|
1959
|
+
const argCount = args.length;
|
|
1960
|
+
for(let argIndex = 0; argIndex < argCount; argIndex++) {
|
|
1961
|
+
const arg = args[argIndex];
|
|
1962
|
+
if (arg == null || arg === false) {
|
|
1963
|
+
// Ignore
|
|
1964
|
+
} else if (typeof arg === "string") {
|
|
1965
|
+
let argLen = arg.length;
|
|
1966
|
+
let nextPos = 0;
|
|
1967
|
+
for(let pos=0; pos<argLen; pos=nextPos+1) {
|
|
1968
|
+
nextPos = findFirst(arg, " .=:#", pos);
|
|
1969
|
+
const next = arg[nextPos];
|
|
1970
|
+
|
|
1971
|
+
if (next === ":" || next === "=") {
|
|
1972
|
+
let key = arg.substring(pos, nextPos);
|
|
1973
|
+
if (next === ':') key = '$' + key; // Style prefix
|
|
1974
|
+
if (nextPos + 1 >= argLen) {
|
|
1975
|
+
applyArg(el, key, args[++argIndex]);
|
|
1976
|
+
break;
|
|
1977
|
+
}
|
|
1978
|
+
if (arg[nextPos+1] === '"') {
|
|
1979
|
+
const endIndex = findFirst(arg, '"', nextPos + 2);
|
|
1980
|
+
const value = arg.substring(nextPos+2, endIndex);
|
|
1981
|
+
applyArg(el, key, value);
|
|
1982
|
+
nextPos = endIndex;
|
|
1983
|
+
} else {
|
|
1984
|
+
const endIndex = findFirst(arg, " ", nextPos + 1);
|
|
1985
|
+
const value = arg.substring(nextPos + 1, endIndex);
|
|
1986
|
+
applyArg(el, key, value);
|
|
1987
|
+
nextPos = endIndex;
|
|
1916
1988
|
}
|
|
1917
|
-
}
|
|
1918
|
-
} else if (arg.indexOf(" ") >= 0) {
|
|
1919
|
-
err = `Tag '${arg}' cannot contain space`;
|
|
1920
|
-
break;
|
|
1921
|
-
} else {
|
|
1922
|
-
// Determine which namespace to use for element creation
|
|
1923
|
-
const useNamespace = currentScope.inSvgNamespace || arg === 'svg';
|
|
1924
|
-
if (useNamespace) {
|
|
1925
|
-
result = document.createElementNS('http://www.w3.org/2000/svg', arg);
|
|
1926
1989
|
} else {
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1990
|
+
if (nextPos > pos) { // Up til this point if non-empty, is a tag
|
|
1991
|
+
const tag = arg.substring(pos, nextPos);
|
|
1992
|
+
// Determine which namespace to use for element creation
|
|
1993
|
+
svg ||= tag === 'svg';
|
|
1994
|
+
let newEl = svg ? document.createElementNS('http://www.w3.org/2000/svg', tag) : document.createElement(tag);
|
|
1995
|
+
addNode(el, newEl);
|
|
1996
|
+
el = newEl;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
if (next === "#") { // The rest of the string is text (or a text arg follows)
|
|
2000
|
+
const text = nextPos + 1 < argLen ? arg.substring(nextPos + 1) : args[++argIndex];
|
|
2001
|
+
applyArg(el, "text", text);
|
|
2002
|
+
break;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
if (next === ".") { // Class name
|
|
2006
|
+
let classEnd = findFirst(arg, " #=.", nextPos + 1);
|
|
2007
|
+
if (arg[classEnd] === '=' && classEnd + 1 >= argLen) {
|
|
2008
|
+
// Conditional class name. Pass to applyArg including the leading '.'
|
|
2009
|
+
applyArg(el, arg.substring(nextPos, classEnd), args[++argIndex]);
|
|
2010
|
+
nextPos = classEnd;
|
|
2011
|
+
} else {
|
|
2012
|
+
let className: any = arg.substring(nextPos + 1, classEnd);
|
|
2013
|
+
el.classList.add(className || args[++argIndex]);
|
|
2014
|
+
nextPos = classEnd - 1;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
1941
2017
|
}
|
|
1942
|
-
|
|
1943
|
-
newScope.lastChild = result.lastChild || undefined;
|
|
1944
|
-
if (topRedrawScope === currentScope) topRedrawScope = newScope;
|
|
1945
|
-
currentScope = newScope;
|
|
1946
2018
|
}
|
|
1947
2019
|
} else if (typeof arg === "object") {
|
|
1948
2020
|
if (arg.constructor !== Object) {
|
|
1949
2021
|
if (arg instanceof Node) {
|
|
1950
|
-
addNode(arg);
|
|
2022
|
+
addNode(el, arg);
|
|
1951
2023
|
if (arg instanceof Element) {
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
currentScope = new ChainedScope(arg, true);
|
|
1955
|
-
currentScope.lastChild = arg.lastChild || undefined;
|
|
2024
|
+
el = arg;
|
|
2025
|
+
svg = arg.namespaceURI === 'http://www.w3.org/2000/svg';
|
|
1956
2026
|
}
|
|
1957
2027
|
} else {
|
|
1958
|
-
|
|
1959
|
-
break;
|
|
2028
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
1960
2029
|
}
|
|
1961
2030
|
} else {
|
|
1962
2031
|
for (const key of Object.keys(arg)) {
|
|
1963
|
-
|
|
1964
|
-
applyArg(key, val);
|
|
2032
|
+
applyArg(el, key, arg[key]);
|
|
1965
2033
|
}
|
|
1966
2034
|
}
|
|
1967
2035
|
} else if (typeof arg === "function") {
|
|
1968
|
-
new RegularScope(
|
|
2036
|
+
new RegularScope(el, svg, arg);
|
|
1969
2037
|
} else {
|
|
1970
|
-
|
|
1971
|
-
break;
|
|
2038
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
1972
2039
|
}
|
|
1973
2040
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
2041
|
+
return el;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
function findFirst(str: string, chars: string, startPos: number): number {
|
|
2045
|
+
if (chars.length === 1) {
|
|
2046
|
+
const idx = str.indexOf(chars, startPos);
|
|
2047
|
+
return idx >= 0 ? idx : str.length;
|
|
1976
2048
|
}
|
|
1977
|
-
|
|
1978
|
-
|
|
2049
|
+
const strLen = str.length;
|
|
2050
|
+
for (let i = startPos; i < strLen; i++) {
|
|
2051
|
+
if (chars.indexOf(str[i]) >= 0) return i;
|
|
2052
|
+
}
|
|
2053
|
+
return strLen;
|
|
1979
2054
|
}
|
|
1980
2055
|
|
|
1981
2056
|
let cssCount = 0;
|
|
@@ -2017,8 +2092,8 @@ let cssCount = 0;
|
|
|
2017
2092
|
*
|
|
2018
2093
|
* // Apply the styles
|
|
2019
2094
|
* $(scopeClass, () => { // Add class to the div
|
|
2020
|
-
* $(
|
|
2021
|
-
* $('div.child-element
|
|
2095
|
+
* $(`#Scoped content`);
|
|
2096
|
+
* $('div.child-element#Child'); // .AbdStl1 .child-element rule applies
|
|
2022
2097
|
* });
|
|
2023
2098
|
* ```
|
|
2024
2099
|
*
|
|
@@ -2034,13 +2109,13 @@ let cssCount = 0;
|
|
|
2034
2109
|
* }
|
|
2035
2110
|
* }, true); // Pass true for global
|
|
2036
2111
|
*
|
|
2037
|
-
* $('a
|
|
2112
|
+
* $('a#Styled link');
|
|
2038
2113
|
* ```
|
|
2039
2114
|
*/
|
|
2040
2115
|
export function insertCss(style: object, global = false): string {
|
|
2041
2116
|
const prefix = global ? "" : `.AbdStl${++cssCount}`;
|
|
2042
2117
|
const css = styleToCss(style, prefix);
|
|
2043
|
-
if (css) $(`style
|
|
2118
|
+
if (css) $(`style#${css}`);
|
|
2044
2119
|
return prefix;
|
|
2045
2120
|
}
|
|
2046
2121
|
|
|
@@ -2069,8 +2144,7 @@ function styleToCss(style: object, prefix: string): string {
|
|
|
2069
2144
|
return rules;
|
|
2070
2145
|
}
|
|
2071
2146
|
|
|
2072
|
-
function applyArg(key: string, value: any) {
|
|
2073
|
-
const el = currentScope.parentElement;
|
|
2147
|
+
function applyArg(el: Element, key: string, value: any) {
|
|
2074
2148
|
if (typeof value === "object" && value !== null && value[TARGET_SYMBOL]) {
|
|
2075
2149
|
// Value is a proxy
|
|
2076
2150
|
if (key === "bind") {
|
|
@@ -2094,11 +2168,11 @@ function applyArg(key: string, value: any) {
|
|
|
2094
2168
|
// Do nothing
|
|
2095
2169
|
} else if (key in SPECIAL_PROPS) {
|
|
2096
2170
|
// Special property
|
|
2097
|
-
SPECIAL_PROPS[key](value);
|
|
2171
|
+
SPECIAL_PROPS[key](el, value);
|
|
2098
2172
|
} else if (typeof value === "function") {
|
|
2099
2173
|
// Event listener
|
|
2100
2174
|
el.addEventListener(key, value);
|
|
2101
|
-
clean(() => el.removeEventListener(key, value));
|
|
2175
|
+
if (el === currentScope.el) clean(() => el.removeEventListener(key, value));
|
|
2102
2176
|
} else if (
|
|
2103
2177
|
value === true ||
|
|
2104
2178
|
value === false ||
|
|
@@ -2143,7 +2217,7 @@ let onError: (error: Error) => boolean | undefined = defaultOnError;
|
|
|
2143
2217
|
*
|
|
2144
2218
|
* try {
|
|
2145
2219
|
* // Attempt to show a custom message in the UI
|
|
2146
|
-
* $('div.error-message
|
|
2220
|
+
* $('div.error-message#Oops, something went wrong!');
|
|
2147
2221
|
* } catch (e) {
|
|
2148
2222
|
* // Ignore errors during error handling itself
|
|
2149
2223
|
* }
|
|
@@ -2200,7 +2274,7 @@ export function setErrorHandler(
|
|
|
2200
2274
|
* ```
|
|
2201
2275
|
*/
|
|
2202
2276
|
export function getParentElement(): Element {
|
|
2203
|
-
return currentScope.
|
|
2277
|
+
return currentScope.el;
|
|
2204
2278
|
}
|
|
2205
2279
|
|
|
2206
2280
|
/**
|
|
@@ -2222,7 +2296,7 @@ export function getParentElement(): Element {
|
|
|
2222
2296
|
*
|
|
2223
2297
|
* // Show the array items and maintain the sum
|
|
2224
2298
|
* onEach(myArray, (item, index) => {
|
|
2225
|
-
* $(`code
|
|
2299
|
+
* $(`code#${index}→${item}`);
|
|
2226
2300
|
* // We'll update sum.value using peek, as += first does a read, but
|
|
2227
2301
|
* // we don't want to subscribe.
|
|
2228
2302
|
* peek(() => sum.value += item);
|
|
@@ -2265,14 +2339,14 @@ export function clean(cleaner: () => void) {
|
|
|
2265
2339
|
*
|
|
2266
2340
|
* $('main', () => {
|
|
2267
2341
|
* console.log('Welcome');
|
|
2268
|
-
* $('h3
|
|
2342
|
+
* $('h3#Welcome, ' + data.user); // Reactive text
|
|
2269
2343
|
*
|
|
2270
2344
|
* derive(() => {
|
|
2271
2345
|
* // When data.notifications changes, only this inner scope reruns,
|
|
2272
2346
|
* // leaving the `<p>Welcome, ..</p>` untouched.
|
|
2273
2347
|
* console.log('Notifications');
|
|
2274
|
-
* $('code.notification-badge
|
|
2275
|
-
* $('a
|
|
2348
|
+
* $('code.notification-badge#' + data.notifications);
|
|
2349
|
+
* $('a#Notify!', {click: () => data.notifications++});
|
|
2276
2350
|
* });
|
|
2277
2351
|
* });
|
|
2278
2352
|
* ```
|
|
@@ -2286,7 +2360,7 @@ export function clean(cleaner: () => void) {
|
|
|
2286
2360
|
* const double = derive(() => counter.value * 2);
|
|
2287
2361
|
*
|
|
2288
2362
|
* $('h3', () => {
|
|
2289
|
-
* $(
|
|
2363
|
+
* $(`#counter=${counter.value} double=${double.value}`);
|
|
2290
2364
|
* })
|
|
2291
2365
|
* ```
|
|
2292
2366
|
*
|
|
@@ -2294,7 +2368,7 @@ export function clean(cleaner: () => void) {
|
|
|
2294
2368
|
* @param func Func without a return value.
|
|
2295
2369
|
*/
|
|
2296
2370
|
export function derive<T>(func: () => T): ValueRef<T> {
|
|
2297
|
-
return new ResultScope<T>(
|
|
2371
|
+
return new ResultScope<T>(func).result;
|
|
2298
2372
|
}
|
|
2299
2373
|
|
|
2300
2374
|
/**
|
|
@@ -2326,12 +2400,12 @@ export function derive<T>(func: () => T): ValueRef<T> {
|
|
|
2326
2400
|
* setInterval(() => runTime.value++, 1000);
|
|
2327
2401
|
*
|
|
2328
2402
|
* mount(document.getElementById('app-root'), () => {
|
|
2329
|
-
* $('h4
|
|
2330
|
-
* $(`p
|
|
2403
|
+
* $('h4#Aberdeen App');
|
|
2404
|
+
* $(`p#Run time: ${runTime.value}s`);
|
|
2331
2405
|
* // Conditionally render some content somewhere else in the static page
|
|
2332
2406
|
* if (runTime.value&1) {
|
|
2333
2407
|
* mount(document.getElementById('title-extra'), () =>
|
|
2334
|
-
* $(`i
|
|
2408
|
+
* $(`i#(${runTime.value}s)`)
|
|
2335
2409
|
* );
|
|
2336
2410
|
* }
|
|
2337
2411
|
* });
|
|
@@ -2664,7 +2738,7 @@ export function partition<
|
|
|
2664
2738
|
func: (value: IN_V, key: KeyToString<IN_K>) => undefined | OUT_K | OUT_K[],
|
|
2665
2739
|
): Record<OUT_K, Record<KeyToString<IN_K>, IN_V>> {
|
|
2666
2740
|
const unproxiedOut = {} as Record<OUT_K, Record<KeyToString<IN_K>, IN_V>>;
|
|
2667
|
-
const out =
|
|
2741
|
+
const out = optProxy(unproxiedOut);
|
|
2668
2742
|
onEach(source, (item: IN_V, key: KeyToString<IN_K>) => {
|
|
2669
2743
|
const rsp = func(item, key);
|
|
2670
2744
|
if (rsp != null) {
|
|
@@ -2706,7 +2780,7 @@ export function partition<
|
|
|
2706
2780
|
* items: ['a', 'b']
|
|
2707
2781
|
* });
|
|
2708
2782
|
*
|
|
2709
|
-
* $('h2
|
|
2783
|
+
* $('h2#Live State Dump');
|
|
2710
2784
|
* dump(state);
|
|
2711
2785
|
*
|
|
2712
2786
|
* // Change state later, the dump in the DOM will update
|
|
@@ -2715,24 +2789,22 @@ export function partition<
|
|
|
2715
2789
|
*/
|
|
2716
2790
|
export function dump<T>(data: T): T {
|
|
2717
2791
|
if (data && typeof data === "object") {
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
label = "<Map>";
|
|
2792
|
+
const name = data.constructor.name.toLowerCase() || "unknown object";
|
|
2793
|
+
$(`#<${name}>`);
|
|
2794
|
+
if (NO_COPY in data ) {
|
|
2795
|
+
$("# [NO_COPY]");
|
|
2723
2796
|
} else {
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
dump(value);
|
|
2797
|
+
$("ul", () => {
|
|
2798
|
+
onEach(data as any, (value, key) => {
|
|
2799
|
+
$("li", () => {
|
|
2800
|
+
$(`#${JSON.stringify(key)}: `);
|
|
2801
|
+
dump(value);
|
|
2802
|
+
});
|
|
2731
2803
|
});
|
|
2732
2804
|
});
|
|
2733
|
-
}
|
|
2734
|
-
} else {
|
|
2735
|
-
$(
|
|
2805
|
+
}
|
|
2806
|
+
} else if (data !== undefined) {
|
|
2807
|
+
$("#" + JSON.stringify(data));
|
|
2736
2808
|
}
|
|
2737
2809
|
return data;
|
|
2738
2810
|
}
|
|
@@ -2754,7 +2826,7 @@ function handleError(e: any, showMessage: boolean) {
|
|
|
2754
2826
|
console.error(e);
|
|
2755
2827
|
}
|
|
2756
2828
|
try {
|
|
2757
|
-
if (showMessage) $("div.aberdeen-error
|
|
2829
|
+
if (showMessage) $("div.aberdeen-error#Error");
|
|
2758
2830
|
} catch {
|
|
2759
2831
|
// Error while adding the error marker to the DOM. Apparently, we're in
|
|
2760
2832
|
// an awkward context. The error should already have been logged by
|