aberdeen 1.2.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 +20 -12
- package/README.md.bak +212 -0
- package/dist/aberdeen.d.ts +47 -30
- package/dist/aberdeen.js +142 -160
- package/dist/aberdeen.js.map +3 -3
- package/dist-min/aberdeen.js +5 -5
- package/dist-min/aberdeen.js.map +3 -3
- package/html-to-aberdeen +397 -0
- package/package.json +1 -1
- package/src/aberdeen.ts +196 -197
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
|
*
|
|
@@ -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,10 +1294,22 @@ function optProxy(value: any): any {
|
|
|
1289
1294
|
return proxied;
|
|
1290
1295
|
}
|
|
1291
1296
|
|
|
1292
|
-
|
|
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
|
+
*/
|
|
1293
1304
|
busy: boolean;
|
|
1294
|
-
|
|
1305
|
+
/**
|
|
1306
|
+
* If the promise has resolved, this contains the resolved value.
|
|
1307
|
+
*/
|
|
1295
1308
|
value?: T;
|
|
1309
|
+
/**
|
|
1310
|
+
* If the promise has rejected, this contains the rejection error.
|
|
1311
|
+
*/
|
|
1312
|
+
error?: any;
|
|
1296
1313
|
}
|
|
1297
1314
|
|
|
1298
1315
|
export function proxy<T extends any>(target: Promise<T>): PromiseProxy<T>;
|
|
@@ -1556,7 +1573,7 @@ function copyRecursive<T extends object>(dst: T, src: T, flags: number): boolean
|
|
|
1556
1573
|
}
|
|
1557
1574
|
else if (dstValue !== srcValue) {
|
|
1558
1575
|
if (srcValue && typeof srcValue === "object") {
|
|
1559
|
-
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor) {
|
|
1576
|
+
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor && !(NO_COPY in srcValue)) {
|
|
1560
1577
|
changed = copyRecursive(dstValue, srcValue, flags) || changed;
|
|
1561
1578
|
continue;
|
|
1562
1579
|
}
|
|
@@ -1592,7 +1609,7 @@ function copyRecursive<T extends object>(dst: T, src: T, flags: number): boolean
|
|
|
1592
1609
|
if (dstValue === undefined && !dst.has(key)) dstValue = EMPTY;
|
|
1593
1610
|
if (dstValue !== srcValue) {
|
|
1594
1611
|
if (srcValue && typeof srcValue === "object") {
|
|
1595
|
-
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor) {
|
|
1612
|
+
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor && !(NO_COPY in srcValue)) {
|
|
1596
1613
|
changed = copyRecursive(dstValue, srcValue, flags) || changed;
|
|
1597
1614
|
continue;
|
|
1598
1615
|
}
|
|
@@ -1625,7 +1642,7 @@ function copyRecursive<T extends object>(dst: T, src: T, flags: number): boolean
|
|
|
1625
1642
|
const dstValue = dst.hasOwnProperty(key) ? dst[key] : EMPTY;
|
|
1626
1643
|
if (dstValue !== srcValue) {
|
|
1627
1644
|
if (srcValue && typeof srcValue === "object") {
|
|
1628
|
-
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor) {
|
|
1645
|
+
if (typeof dstValue === "object" && dstValue && srcValue.constructor === dstValue.constructor && !(NO_COPY in srcValue)) {
|
|
1629
1646
|
changed = copyRecursive(dstValue as typeof srcValue, srcValue, flags) || changed;
|
|
1630
1647
|
continue;
|
|
1631
1648
|
}
|
|
@@ -1661,6 +1678,16 @@ const MERGE = 1;
|
|
|
1661
1678
|
const COPY_SUBSCRIBE = 32;
|
|
1662
1679
|
const COPY_EMIT = 64;
|
|
1663
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
|
+
|
|
1664
1691
|
/**
|
|
1665
1692
|
* Clone an (optionally proxied) object or array.
|
|
1666
1693
|
*
|
|
@@ -1669,6 +1696,7 @@ const COPY_EMIT = 64;
|
|
|
1669
1696
|
* @returns A new unproxied array or object (of the same type as `src`), containing a deep copy of `src`.
|
|
1670
1697
|
*/
|
|
1671
1698
|
export function clone<T extends object>(src: T): T {
|
|
1699
|
+
if (NO_COPY in src) return src;
|
|
1672
1700
|
// Create an empty object of the same type
|
|
1673
1701
|
const copied = Array.isArray(src) ? [] : src instanceof Map ? new Map() : Object.create(Object.getPrototypeOf(src));
|
|
1674
1702
|
// Copy all properties to it. This doesn't need to emit anything, and because
|
|
@@ -1726,7 +1754,7 @@ const refHandler: ProxyHandler<RefTarget> = {
|
|
|
1726
1754
|
* });
|
|
1727
1755
|
*
|
|
1728
1756
|
* // Usage as a dynamic property, causes a TextNode with just the name to be created and live-updated
|
|
1729
|
-
* $('p
|
|
1757
|
+
* $('p#Selected color: ', {
|
|
1730
1758
|
* text: ref(formData, 'color'),
|
|
1731
1759
|
* $color: ref(formData, 'color')
|
|
1732
1760
|
* });
|
|
@@ -1789,9 +1817,8 @@ function applyBind(el: HTMLInputElement, target: any) {
|
|
|
1789
1817
|
});
|
|
1790
1818
|
}
|
|
1791
1819
|
|
|
1792
|
-
const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
1793
|
-
create: (value: any) => {
|
|
1794
|
-
const el = currentScope.parentElement;
|
|
1820
|
+
const SPECIAL_PROPS: { [key: string]: (el: Element, value: any) => void } = {
|
|
1821
|
+
create: (el: Element, value: any) => {
|
|
1795
1822
|
if (currentScope !== topRedrawScope) return;
|
|
1796
1823
|
if (typeof value === "function") {
|
|
1797
1824
|
value(el);
|
|
@@ -1805,19 +1832,18 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1805
1832
|
})();
|
|
1806
1833
|
}
|
|
1807
1834
|
},
|
|
1808
|
-
destroy: (value: any) => {
|
|
1809
|
-
const el = currentScope.parentElement;
|
|
1835
|
+
destroy: (el: Element, value: any) => {
|
|
1810
1836
|
onDestroyMap.set(el, value);
|
|
1811
1837
|
},
|
|
1812
|
-
html: (value: any) => {
|
|
1838
|
+
html: (el: Element, value: any) => {
|
|
1813
1839
|
const tmpParent = document.createElement(
|
|
1814
|
-
currentScope.
|
|
1840
|
+
currentScope.el.tagName,
|
|
1815
1841
|
);
|
|
1816
1842
|
tmpParent.innerHTML = `${value}`;
|
|
1817
|
-
while (tmpParent.firstChild) addNode(tmpParent.firstChild);
|
|
1843
|
+
while (tmpParent.firstChild) addNode(el, tmpParent.firstChild);
|
|
1818
1844
|
},
|
|
1819
|
-
text: (value: any) => {
|
|
1820
|
-
addNode(document.createTextNode(value));
|
|
1845
|
+
text: (el: Element, value: any) => {
|
|
1846
|
+
addNode(el, document.createTextNode(value));
|
|
1821
1847
|
},
|
|
1822
1848
|
};
|
|
1823
1849
|
|
|
@@ -1829,12 +1855,12 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1829
1855
|
* @param {...(string | function | object | false | undefined | null)} args - Any number of arguments can be given. How they're interpreted depends on their types:
|
|
1830
1856
|
*
|
|
1831
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.
|
|
1832
|
-
* The format of a string is: (**tag** | `.` **class** | **key**=**val** | **key**="**long val**")* ('
|
|
1858
|
+
* The format of a string is: (**tag** | `.` **class** | **key**=**val** | **key**="**long val**")* ('#' **text** | **key**=)?
|
|
1833
1859
|
* So there can be:
|
|
1834
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.
|
|
1835
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.
|
|
1836
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).
|
|
1837
|
-
* - The string may end in a '
|
|
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.
|
|
1838
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")`.
|
|
1839
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}).
|
|
1840
1866
|
* - `object`: When an object is passed in, its key-value pairs are used to modify the *current* element in the following ways...
|
|
@@ -1850,7 +1876,7 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1850
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:
|
|
1851
1877
|
* ```typescript
|
|
1852
1878
|
* const myColor = proxy('red');
|
|
1853
|
-
* $('p
|
|
1879
|
+
* $('p#Test', {$color: myColor, click: () => myColor.value = 'yellow'})
|
|
1854
1880
|
* // Clicking the text will cause it to change color without recreating the <p> itself
|
|
1855
1881
|
* ```
|
|
1856
1882
|
* This is often used together with {@link ref}, in order to use properties other than `.value`.
|
|
@@ -1863,7 +1889,7 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1863
1889
|
*
|
|
1864
1890
|
* @example Create Element
|
|
1865
1891
|
* ```typescript
|
|
1866
|
-
* $('button.secondary.outline
|
|
1892
|
+
* $('button.secondary.outline#Submit', {
|
|
1867
1893
|
* disabled: false,
|
|
1868
1894
|
* click: () => console.log('Clicked!'),
|
|
1869
1895
|
* $color: 'red'
|
|
@@ -1880,7 +1906,7 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1880
1906
|
*
|
|
1881
1907
|
* @example Create Nested Elements
|
|
1882
1908
|
* ```typescript
|
|
1883
|
-
* let inputElement: Element = $('label
|
|
1909
|
+
* let inputElement: Element = $('label#Click me', 'input', {type: 'checkbox'});
|
|
1884
1910
|
* // You should usually not touch raw DOM elements, unless when integrating
|
|
1885
1911
|
* // with non-Aberdeen code.
|
|
1886
1912
|
* console.log('DOM element:', inputElement);
|
|
@@ -1891,8 +1917,8 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1891
1917
|
* const state = proxy({ count: 0 });
|
|
1892
1918
|
* $('div', () => { // Outer element
|
|
1893
1919
|
* // This scope re-renders when state.count changes
|
|
1894
|
-
* $(`p
|
|
1895
|
-
* $('button
|
|
1920
|
+
* $(`p#Count is ${state.count}`);
|
|
1921
|
+
* $('button#Increment', { click: () => state.count++ });
|
|
1896
1922
|
* });
|
|
1897
1923
|
* ```
|
|
1898
1924
|
*
|
|
@@ -1901,17 +1927,17 @@ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
|
|
|
1901
1927
|
* const user = proxy({ name: '' });
|
|
1902
1928
|
* $('input', { placeholder: 'Name', bind: ref(user, 'name') });
|
|
1903
1929
|
* $('h3', () => { // Reactive scope
|
|
1904
|
-
* $(
|
|
1930
|
+
* $(`#Hello ${user.name || 'stranger'}`);
|
|
1905
1931
|
* });
|
|
1906
1932
|
* ```
|
|
1907
1933
|
*
|
|
1908
1934
|
* @example Conditional Rendering
|
|
1909
1935
|
* ```typescript
|
|
1910
1936
|
* const show = proxy(false);
|
|
1911
|
-
* $('button', { click: () => show.value = !show.value }, () => $(show.value ? '
|
|
1937
|
+
* $('button', { click: () => show.value = !show.value }, () => $(show.value ? '#Hide' : '#Show'));
|
|
1912
1938
|
* $(() => { // Reactive scope
|
|
1913
1939
|
* if (show.value) {
|
|
1914
|
-
* $('p
|
|
1940
|
+
* $('p#Details are visible!');
|
|
1915
1941
|
* }
|
|
1916
1942
|
* });
|
|
1917
1943
|
* ```
|
|
@@ -1927,128 +1953,104 @@ export function $(
|
|
|
1927
1953
|
| Record<string, any>
|
|
1928
1954
|
)[]
|
|
1929
1955
|
): undefined | Element {
|
|
1930
|
-
let
|
|
1931
|
-
let
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
if (
|
|
1937
|
-
applyArg(nextArgIsProp, arg);
|
|
1938
|
-
nextArgIsProp = undefined;
|
|
1939
|
-
} else if (arg == null || arg === false) {
|
|
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) {
|
|
1940
1963
|
// Ignore
|
|
1941
1964
|
} else if (typeof arg === "string") {
|
|
1942
|
-
let pos = 0;
|
|
1943
1965
|
let argLen = arg.length;
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
pos = closeIndex + 1;
|
|
1962
|
-
if (arg[pos] === ' ') pos++;
|
|
1963
|
-
}
|
|
1964
|
-
applyArg(prop, value);
|
|
1965
|
-
continue;
|
|
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;
|
|
1966
1983
|
} else {
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1984
|
+
const endIndex = findFirst(arg, " ", nextPos + 1);
|
|
1985
|
+
const value = arg.substring(nextPos + 1, endIndex);
|
|
1986
|
+
applyArg(el, key, value);
|
|
1987
|
+
nextPos = endIndex;
|
|
1970
1988
|
}
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
if (pos < argLen) throw new Error(`No value given for '${part}'`);
|
|
1980
|
-
nextArgIsProp = 'text';
|
|
1981
|
-
break;
|
|
1989
|
+
} else {
|
|
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;
|
|
1982
1997
|
}
|
|
1983
|
-
pos = argLen;
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
|
-
let classes: undefined | string;
|
|
1987
|
-
const classPos = part.indexOf(".");
|
|
1988
|
-
if (classPos >= 0) {
|
|
1989
|
-
classes = part.substring(classPos + 1);
|
|
1990
|
-
part = part.substring(0, classPos);
|
|
1991
|
-
}
|
|
1992
1998
|
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
result = document.createElementNS('http://www.w3.org/2000/svg', part);
|
|
1998
|
-
} else {
|
|
1999
|
-
result = document.createElement(part);
|
|
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;
|
|
2000
2003
|
}
|
|
2001
|
-
addNode(result);
|
|
2002
|
-
if (!savedCurrentScope) savedCurrentScope = currentScope;
|
|
2003
|
-
const newScope = new ChainedScope(result, true);
|
|
2004
|
-
|
|
2005
|
-
// SVG namespace should be inherited by children
|
|
2006
|
-
if (svg) newScope.inSvgNamespace = true;
|
|
2007
|
-
|
|
2008
|
-
if (topRedrawScope === currentScope) topRedrawScope = newScope;
|
|
2009
|
-
currentScope = newScope;
|
|
2010
|
-
}
|
|
2011
2004
|
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
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
|
+
}
|
|
2018
2016
|
}
|
|
2019
2017
|
}
|
|
2020
2018
|
}
|
|
2021
2019
|
} else if (typeof arg === "object") {
|
|
2022
2020
|
if (arg.constructor !== Object) {
|
|
2023
2021
|
if (arg instanceof Node) {
|
|
2024
|
-
addNode(arg);
|
|
2022
|
+
addNode(el, arg);
|
|
2025
2023
|
if (arg instanceof Element) {
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
currentScope = new ChainedScope(arg, true);
|
|
2029
|
-
currentScope.lastChild = arg.lastChild || undefined;
|
|
2024
|
+
el = arg;
|
|
2025
|
+
svg = arg.namespaceURI === 'http://www.w3.org/2000/svg';
|
|
2030
2026
|
}
|
|
2031
2027
|
} else {
|
|
2032
|
-
|
|
2033
|
-
break;
|
|
2028
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
2034
2029
|
}
|
|
2035
2030
|
} else {
|
|
2036
2031
|
for (const key of Object.keys(arg)) {
|
|
2037
|
-
|
|
2038
|
-
applyArg(key, val);
|
|
2032
|
+
applyArg(el, key, arg[key]);
|
|
2039
2033
|
}
|
|
2040
2034
|
}
|
|
2041
2035
|
} else if (typeof arg === "function") {
|
|
2042
|
-
new RegularScope(
|
|
2036
|
+
new RegularScope(el, svg, arg);
|
|
2043
2037
|
} else {
|
|
2044
|
-
|
|
2045
|
-
break;
|
|
2038
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
2046
2039
|
}
|
|
2047
2040
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
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;
|
|
2048
|
+
}
|
|
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;
|
|
2052
2054
|
}
|
|
2053
2055
|
|
|
2054
2056
|
let cssCount = 0;
|
|
@@ -2090,8 +2092,8 @@ let cssCount = 0;
|
|
|
2090
2092
|
*
|
|
2091
2093
|
* // Apply the styles
|
|
2092
2094
|
* $(scopeClass, () => { // Add class to the div
|
|
2093
|
-
* $(
|
|
2094
|
-
* $('div.child-element
|
|
2095
|
+
* $(`#Scoped content`);
|
|
2096
|
+
* $('div.child-element#Child'); // .AbdStl1 .child-element rule applies
|
|
2095
2097
|
* });
|
|
2096
2098
|
* ```
|
|
2097
2099
|
*
|
|
@@ -2107,13 +2109,13 @@ let cssCount = 0;
|
|
|
2107
2109
|
* }
|
|
2108
2110
|
* }, true); // Pass true for global
|
|
2109
2111
|
*
|
|
2110
|
-
* $('a
|
|
2112
|
+
* $('a#Styled link');
|
|
2111
2113
|
* ```
|
|
2112
2114
|
*/
|
|
2113
2115
|
export function insertCss(style: object, global = false): string {
|
|
2114
2116
|
const prefix = global ? "" : `.AbdStl${++cssCount}`;
|
|
2115
2117
|
const css = styleToCss(style, prefix);
|
|
2116
|
-
if (css) $(`style
|
|
2118
|
+
if (css) $(`style#${css}`);
|
|
2117
2119
|
return prefix;
|
|
2118
2120
|
}
|
|
2119
2121
|
|
|
@@ -2142,8 +2144,7 @@ function styleToCss(style: object, prefix: string): string {
|
|
|
2142
2144
|
return rules;
|
|
2143
2145
|
}
|
|
2144
2146
|
|
|
2145
|
-
function applyArg(key: string, value: any) {
|
|
2146
|
-
const el = currentScope.parentElement;
|
|
2147
|
+
function applyArg(el: Element, key: string, value: any) {
|
|
2147
2148
|
if (typeof value === "object" && value !== null && value[TARGET_SYMBOL]) {
|
|
2148
2149
|
// Value is a proxy
|
|
2149
2150
|
if (key === "bind") {
|
|
@@ -2167,11 +2168,11 @@ function applyArg(key: string, value: any) {
|
|
|
2167
2168
|
// Do nothing
|
|
2168
2169
|
} else if (key in SPECIAL_PROPS) {
|
|
2169
2170
|
// Special property
|
|
2170
|
-
SPECIAL_PROPS[key](value);
|
|
2171
|
+
SPECIAL_PROPS[key](el, value);
|
|
2171
2172
|
} else if (typeof value === "function") {
|
|
2172
2173
|
// Event listener
|
|
2173
2174
|
el.addEventListener(key, value);
|
|
2174
|
-
clean(() => el.removeEventListener(key, value));
|
|
2175
|
+
if (el === currentScope.el) clean(() => el.removeEventListener(key, value));
|
|
2175
2176
|
} else if (
|
|
2176
2177
|
value === true ||
|
|
2177
2178
|
value === false ||
|
|
@@ -2216,7 +2217,7 @@ let onError: (error: Error) => boolean | undefined = defaultOnError;
|
|
|
2216
2217
|
*
|
|
2217
2218
|
* try {
|
|
2218
2219
|
* // Attempt to show a custom message in the UI
|
|
2219
|
-
* $('div.error-message
|
|
2220
|
+
* $('div.error-message#Oops, something went wrong!');
|
|
2220
2221
|
* } catch (e) {
|
|
2221
2222
|
* // Ignore errors during error handling itself
|
|
2222
2223
|
* }
|
|
@@ -2273,7 +2274,7 @@ export function setErrorHandler(
|
|
|
2273
2274
|
* ```
|
|
2274
2275
|
*/
|
|
2275
2276
|
export function getParentElement(): Element {
|
|
2276
|
-
return currentScope.
|
|
2277
|
+
return currentScope.el;
|
|
2277
2278
|
}
|
|
2278
2279
|
|
|
2279
2280
|
/**
|
|
@@ -2295,7 +2296,7 @@ export function getParentElement(): Element {
|
|
|
2295
2296
|
*
|
|
2296
2297
|
* // Show the array items and maintain the sum
|
|
2297
2298
|
* onEach(myArray, (item, index) => {
|
|
2298
|
-
* $(`code
|
|
2299
|
+
* $(`code#${index}→${item}`);
|
|
2299
2300
|
* // We'll update sum.value using peek, as += first does a read, but
|
|
2300
2301
|
* // we don't want to subscribe.
|
|
2301
2302
|
* peek(() => sum.value += item);
|
|
@@ -2338,14 +2339,14 @@ export function clean(cleaner: () => void) {
|
|
|
2338
2339
|
*
|
|
2339
2340
|
* $('main', () => {
|
|
2340
2341
|
* console.log('Welcome');
|
|
2341
|
-
* $('h3
|
|
2342
|
+
* $('h3#Welcome, ' + data.user); // Reactive text
|
|
2342
2343
|
*
|
|
2343
2344
|
* derive(() => {
|
|
2344
2345
|
* // When data.notifications changes, only this inner scope reruns,
|
|
2345
2346
|
* // leaving the `<p>Welcome, ..</p>` untouched.
|
|
2346
2347
|
* console.log('Notifications');
|
|
2347
|
-
* $('code.notification-badge
|
|
2348
|
-
* $('a
|
|
2348
|
+
* $('code.notification-badge#' + data.notifications);
|
|
2349
|
+
* $('a#Notify!', {click: () => data.notifications++});
|
|
2349
2350
|
* });
|
|
2350
2351
|
* });
|
|
2351
2352
|
* ```
|
|
@@ -2359,7 +2360,7 @@ export function clean(cleaner: () => void) {
|
|
|
2359
2360
|
* const double = derive(() => counter.value * 2);
|
|
2360
2361
|
*
|
|
2361
2362
|
* $('h3', () => {
|
|
2362
|
-
* $(
|
|
2363
|
+
* $(`#counter=${counter.value} double=${double.value}`);
|
|
2363
2364
|
* })
|
|
2364
2365
|
* ```
|
|
2365
2366
|
*
|
|
@@ -2367,7 +2368,7 @@ export function clean(cleaner: () => void) {
|
|
|
2367
2368
|
* @param func Func without a return value.
|
|
2368
2369
|
*/
|
|
2369
2370
|
export function derive<T>(func: () => T): ValueRef<T> {
|
|
2370
|
-
return new ResultScope<T>(
|
|
2371
|
+
return new ResultScope<T>(func).result;
|
|
2371
2372
|
}
|
|
2372
2373
|
|
|
2373
2374
|
/**
|
|
@@ -2399,12 +2400,12 @@ export function derive<T>(func: () => T): ValueRef<T> {
|
|
|
2399
2400
|
* setInterval(() => runTime.value++, 1000);
|
|
2400
2401
|
*
|
|
2401
2402
|
* mount(document.getElementById('app-root'), () => {
|
|
2402
|
-
* $('h4
|
|
2403
|
-
* $(`p
|
|
2403
|
+
* $('h4#Aberdeen App');
|
|
2404
|
+
* $(`p#Run time: ${runTime.value}s`);
|
|
2404
2405
|
* // Conditionally render some content somewhere else in the static page
|
|
2405
2406
|
* if (runTime.value&1) {
|
|
2406
2407
|
* mount(document.getElementById('title-extra'), () =>
|
|
2407
|
-
* $(`i
|
|
2408
|
+
* $(`i#(${runTime.value}s)`)
|
|
2408
2409
|
* );
|
|
2409
2410
|
* }
|
|
2410
2411
|
* });
|
|
@@ -2737,7 +2738,7 @@ export function partition<
|
|
|
2737
2738
|
func: (value: IN_V, key: KeyToString<IN_K>) => undefined | OUT_K | OUT_K[],
|
|
2738
2739
|
): Record<OUT_K, Record<KeyToString<IN_K>, IN_V>> {
|
|
2739
2740
|
const unproxiedOut = {} as Record<OUT_K, Record<KeyToString<IN_K>, IN_V>>;
|
|
2740
|
-
const out =
|
|
2741
|
+
const out = optProxy(unproxiedOut);
|
|
2741
2742
|
onEach(source, (item: IN_V, key: KeyToString<IN_K>) => {
|
|
2742
2743
|
const rsp = func(item, key);
|
|
2743
2744
|
if (rsp != null) {
|
|
@@ -2779,7 +2780,7 @@ export function partition<
|
|
|
2779
2780
|
* items: ['a', 'b']
|
|
2780
2781
|
* });
|
|
2781
2782
|
*
|
|
2782
|
-
* $('h2
|
|
2783
|
+
* $('h2#Live State Dump');
|
|
2783
2784
|
* dump(state);
|
|
2784
2785
|
*
|
|
2785
2786
|
* // Change state later, the dump in the DOM will update
|
|
@@ -2788,24 +2789,22 @@ export function partition<
|
|
|
2788
2789
|
*/
|
|
2789
2790
|
export function dump<T>(data: T): T {
|
|
2790
2791
|
if (data && typeof data === "object") {
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
label = "<Map>";
|
|
2792
|
+
const name = data.constructor.name.toLowerCase() || "unknown object";
|
|
2793
|
+
$(`#<${name}>`);
|
|
2794
|
+
if (NO_COPY in data ) {
|
|
2795
|
+
$("# [NO_COPY]");
|
|
2796
2796
|
} else {
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
dump(value);
|
|
2797
|
+
$("ul", () => {
|
|
2798
|
+
onEach(data as any, (value, key) => {
|
|
2799
|
+
$("li", () => {
|
|
2800
|
+
$(`#${JSON.stringify(key)}: `);
|
|
2801
|
+
dump(value);
|
|
2802
|
+
});
|
|
2804
2803
|
});
|
|
2805
2804
|
});
|
|
2806
|
-
}
|
|
2807
|
-
} else {
|
|
2808
|
-
$(
|
|
2805
|
+
}
|
|
2806
|
+
} else if (data !== undefined) {
|
|
2807
|
+
$("#" + JSON.stringify(data));
|
|
2809
2808
|
}
|
|
2810
2809
|
return data;
|
|
2811
2810
|
}
|
|
@@ -2827,7 +2826,7 @@ function handleError(e: any, showMessage: boolean) {
|
|
|
2827
2826
|
console.error(e);
|
|
2828
2827
|
}
|
|
2829
2828
|
try {
|
|
2830
|
-
if (showMessage) $("div.aberdeen-error
|
|
2829
|
+
if (showMessage) $("div.aberdeen-error#Error");
|
|
2831
2830
|
} catch {
|
|
2832
2831
|
// Error while adding the error marker to the DOM. Apparently, we're in
|
|
2833
2832
|
// an awkward context. The error should already have been logged by
|