aberdeen 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -12
- package/README.md.bak +212 -0
- package/dist/aberdeen.d.ts +48 -31
- 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 +197 -207
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,154 +1927,121 @@ 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
|
* ```
|
|
1918
1944
|
*/
|
|
1919
1945
|
|
|
1920
|
-
export function $(
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
)[]
|
|
1929
|
-
): undefined | Element {
|
|
1930
|
-
let savedCurrentScope: undefined | ContentScope;
|
|
1931
|
-
let err: undefined | string;
|
|
1932
|
-
let result: undefined | Element;
|
|
1933
|
-
let nextArgIsProp: undefined | string;
|
|
1934
|
-
|
|
1935
|
-
for (let arg of args) {
|
|
1936
|
-
if (nextArgIsProp) {
|
|
1937
|
-
applyArg(nextArgIsProp, arg);
|
|
1938
|
-
nextArgIsProp = undefined;
|
|
1939
|
-
} else if (arg == null || arg === false) {
|
|
1946
|
+
export function $(...args: any[]): undefined | Element {
|
|
1947
|
+
let el: undefined | Element = currentScope.el;
|
|
1948
|
+
let svg: boolean = currentScope.svg
|
|
1949
|
+
|
|
1950
|
+
const argCount = args.length;
|
|
1951
|
+
for(let argIndex = 0; argIndex < argCount; argIndex++) {
|
|
1952
|
+
const arg = args[argIndex];
|
|
1953
|
+
if (arg == null || arg === false) {
|
|
1940
1954
|
// Ignore
|
|
1941
1955
|
} else if (typeof arg === "string") {
|
|
1942
|
-
let pos = 0;
|
|
1943
1956
|
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;
|
|
1957
|
+
let nextPos = 0;
|
|
1958
|
+
for(let pos=0; pos<argLen; pos=nextPos+1) {
|
|
1959
|
+
nextPos = findFirst(arg, " .=:#", pos);
|
|
1960
|
+
const next = arg[nextPos];
|
|
1961
|
+
|
|
1962
|
+
if (next === ":" || next === "=") {
|
|
1963
|
+
let key = arg.substring(pos, nextPos);
|
|
1964
|
+
if (next === ':') key = '$' + key; // Style prefix
|
|
1965
|
+
if (nextPos + 1 >= argLen) {
|
|
1966
|
+
applyArg(el, key, args[++argIndex]);
|
|
1967
|
+
break;
|
|
1968
|
+
}
|
|
1969
|
+
if (arg[nextPos+1] === '"') {
|
|
1970
|
+
const endIndex = findFirst(arg, '"', nextPos + 2);
|
|
1971
|
+
const value = arg.substring(nextPos+2, endIndex);
|
|
1972
|
+
applyArg(el, key, value);
|
|
1973
|
+
nextPos = endIndex;
|
|
1966
1974
|
} else {
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1975
|
+
const endIndex = findFirst(arg, " ", nextPos + 1);
|
|
1976
|
+
const value = arg.substring(nextPos + 1, endIndex);
|
|
1977
|
+
applyArg(el, key, value);
|
|
1978
|
+
nextPos = endIndex;
|
|
1970
1979
|
}
|
|
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;
|
|
1980
|
+
} else {
|
|
1981
|
+
if (nextPos > pos) { // Up til this point if non-empty, is a tag
|
|
1982
|
+
const tag = arg.substring(pos, nextPos);
|
|
1983
|
+
// Determine which namespace to use for element creation
|
|
1984
|
+
svg ||= tag === 'svg';
|
|
1985
|
+
let newEl = svg ? document.createElementNS('http://www.w3.org/2000/svg', tag) : document.createElement(tag);
|
|
1986
|
+
addNode(el, newEl);
|
|
1987
|
+
el = newEl;
|
|
1982
1988
|
}
|
|
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
1989
|
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
result = document.createElementNS('http://www.w3.org/2000/svg', part);
|
|
1998
|
-
} else {
|
|
1999
|
-
result = document.createElement(part);
|
|
1990
|
+
if (next === "#") { // The rest of the string is text (or a text arg follows)
|
|
1991
|
+
const text = nextPos + 1 < argLen ? arg.substring(nextPos + 1) : args[++argIndex];
|
|
1992
|
+
applyArg(el, "text", text);
|
|
1993
|
+
break;
|
|
2000
1994
|
}
|
|
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
1995
|
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
1996
|
+
if (next === ".") { // Class name
|
|
1997
|
+
let classEnd = findFirst(arg, " #=.", nextPos + 1);
|
|
1998
|
+
if (arg[classEnd] === '=' && classEnd + 1 >= argLen) {
|
|
1999
|
+
// Conditional class name. Pass to applyArg including the leading '.'
|
|
2000
|
+
applyArg(el, arg.substring(nextPos, classEnd), args[++argIndex]);
|
|
2001
|
+
nextPos = classEnd;
|
|
2002
|
+
} else {
|
|
2003
|
+
let className: any = arg.substring(nextPos + 1, classEnd);
|
|
2004
|
+
el.classList.add(className || args[++argIndex]);
|
|
2005
|
+
nextPos = classEnd - 1;
|
|
2006
|
+
}
|
|
2018
2007
|
}
|
|
2019
2008
|
}
|
|
2020
2009
|
}
|
|
2021
2010
|
} else if (typeof arg === "object") {
|
|
2022
2011
|
if (arg.constructor !== Object) {
|
|
2023
2012
|
if (arg instanceof Node) {
|
|
2024
|
-
addNode(arg);
|
|
2013
|
+
addNode(el, arg);
|
|
2025
2014
|
if (arg instanceof Element) {
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
currentScope = new ChainedScope(arg, true);
|
|
2029
|
-
currentScope.lastChild = arg.lastChild || undefined;
|
|
2015
|
+
el = arg;
|
|
2016
|
+
svg = arg.namespaceURI === 'http://www.w3.org/2000/svg';
|
|
2030
2017
|
}
|
|
2031
2018
|
} else {
|
|
2032
|
-
|
|
2033
|
-
break;
|
|
2019
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
2034
2020
|
}
|
|
2035
2021
|
} else {
|
|
2036
2022
|
for (const key of Object.keys(arg)) {
|
|
2037
|
-
|
|
2038
|
-
applyArg(key, val);
|
|
2023
|
+
applyArg(el, key, arg[key]);
|
|
2039
2024
|
}
|
|
2040
2025
|
}
|
|
2041
2026
|
} else if (typeof arg === "function") {
|
|
2042
|
-
new RegularScope(
|
|
2027
|
+
new RegularScope(el, svg, arg);
|
|
2043
2028
|
} else {
|
|
2044
|
-
|
|
2045
|
-
break;
|
|
2029
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
2046
2030
|
}
|
|
2047
2031
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2032
|
+
return el;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function findFirst(str: string, chars: string, startPos: number): number {
|
|
2036
|
+
if (chars.length === 1) {
|
|
2037
|
+
const idx = str.indexOf(chars, startPos);
|
|
2038
|
+
return idx >= 0 ? idx : str.length;
|
|
2039
|
+
}
|
|
2040
|
+
const strLen = str.length;
|
|
2041
|
+
for (let i = startPos; i < strLen; i++) {
|
|
2042
|
+
if (chars.indexOf(str[i]) >= 0) return i;
|
|
2043
|
+
}
|
|
2044
|
+
return strLen;
|
|
2052
2045
|
}
|
|
2053
2046
|
|
|
2054
2047
|
let cssCount = 0;
|
|
@@ -2090,8 +2083,8 @@ let cssCount = 0;
|
|
|
2090
2083
|
*
|
|
2091
2084
|
* // Apply the styles
|
|
2092
2085
|
* $(scopeClass, () => { // Add class to the div
|
|
2093
|
-
* $(
|
|
2094
|
-
* $('div.child-element
|
|
2086
|
+
* $(`#Scoped content`);
|
|
2087
|
+
* $('div.child-element#Child'); // .AbdStl1 .child-element rule applies
|
|
2095
2088
|
* });
|
|
2096
2089
|
* ```
|
|
2097
2090
|
*
|
|
@@ -2107,13 +2100,13 @@ let cssCount = 0;
|
|
|
2107
2100
|
* }
|
|
2108
2101
|
* }, true); // Pass true for global
|
|
2109
2102
|
*
|
|
2110
|
-
* $('a
|
|
2103
|
+
* $('a#Styled link');
|
|
2111
2104
|
* ```
|
|
2112
2105
|
*/
|
|
2113
2106
|
export function insertCss(style: object, global = false): string {
|
|
2114
2107
|
const prefix = global ? "" : `.AbdStl${++cssCount}`;
|
|
2115
2108
|
const css = styleToCss(style, prefix);
|
|
2116
|
-
if (css) $(`style
|
|
2109
|
+
if (css) $(`style#${css}`);
|
|
2117
2110
|
return prefix;
|
|
2118
2111
|
}
|
|
2119
2112
|
|
|
@@ -2142,8 +2135,7 @@ function styleToCss(style: object, prefix: string): string {
|
|
|
2142
2135
|
return rules;
|
|
2143
2136
|
}
|
|
2144
2137
|
|
|
2145
|
-
function applyArg(key: string, value: any) {
|
|
2146
|
-
const el = currentScope.parentElement;
|
|
2138
|
+
function applyArg(el: Element, key: string, value: any) {
|
|
2147
2139
|
if (typeof value === "object" && value !== null && value[TARGET_SYMBOL]) {
|
|
2148
2140
|
// Value is a proxy
|
|
2149
2141
|
if (key === "bind") {
|
|
@@ -2167,11 +2159,11 @@ function applyArg(key: string, value: any) {
|
|
|
2167
2159
|
// Do nothing
|
|
2168
2160
|
} else if (key in SPECIAL_PROPS) {
|
|
2169
2161
|
// Special property
|
|
2170
|
-
SPECIAL_PROPS[key](value);
|
|
2162
|
+
SPECIAL_PROPS[key](el, value);
|
|
2171
2163
|
} else if (typeof value === "function") {
|
|
2172
2164
|
// Event listener
|
|
2173
2165
|
el.addEventListener(key, value);
|
|
2174
|
-
clean(() => el.removeEventListener(key, value));
|
|
2166
|
+
if (el === currentScope.el) clean(() => el.removeEventListener(key, value));
|
|
2175
2167
|
} else if (
|
|
2176
2168
|
value === true ||
|
|
2177
2169
|
value === false ||
|
|
@@ -2216,7 +2208,7 @@ let onError: (error: Error) => boolean | undefined = defaultOnError;
|
|
|
2216
2208
|
*
|
|
2217
2209
|
* try {
|
|
2218
2210
|
* // Attempt to show a custom message in the UI
|
|
2219
|
-
* $('div.error-message
|
|
2211
|
+
* $('div.error-message#Oops, something went wrong!');
|
|
2220
2212
|
* } catch (e) {
|
|
2221
2213
|
* // Ignore errors during error handling itself
|
|
2222
2214
|
* }
|
|
@@ -2273,7 +2265,7 @@ export function setErrorHandler(
|
|
|
2273
2265
|
* ```
|
|
2274
2266
|
*/
|
|
2275
2267
|
export function getParentElement(): Element {
|
|
2276
|
-
return currentScope.
|
|
2268
|
+
return currentScope.el;
|
|
2277
2269
|
}
|
|
2278
2270
|
|
|
2279
2271
|
/**
|
|
@@ -2295,7 +2287,7 @@ export function getParentElement(): Element {
|
|
|
2295
2287
|
*
|
|
2296
2288
|
* // Show the array items and maintain the sum
|
|
2297
2289
|
* onEach(myArray, (item, index) => {
|
|
2298
|
-
* $(`code
|
|
2290
|
+
* $(`code#${index}→${item}`);
|
|
2299
2291
|
* // We'll update sum.value using peek, as += first does a read, but
|
|
2300
2292
|
* // we don't want to subscribe.
|
|
2301
2293
|
* peek(() => sum.value += item);
|
|
@@ -2338,14 +2330,14 @@ export function clean(cleaner: () => void) {
|
|
|
2338
2330
|
*
|
|
2339
2331
|
* $('main', () => {
|
|
2340
2332
|
* console.log('Welcome');
|
|
2341
|
-
* $('h3
|
|
2333
|
+
* $('h3#Welcome, ' + data.user); // Reactive text
|
|
2342
2334
|
*
|
|
2343
2335
|
* derive(() => {
|
|
2344
2336
|
* // When data.notifications changes, only this inner scope reruns,
|
|
2345
2337
|
* // leaving the `<p>Welcome, ..</p>` untouched.
|
|
2346
2338
|
* console.log('Notifications');
|
|
2347
|
-
* $('code.notification-badge
|
|
2348
|
-
* $('a
|
|
2339
|
+
* $('code.notification-badge#' + data.notifications);
|
|
2340
|
+
* $('a#Notify!', {click: () => data.notifications++});
|
|
2349
2341
|
* });
|
|
2350
2342
|
* });
|
|
2351
2343
|
* ```
|
|
@@ -2359,7 +2351,7 @@ export function clean(cleaner: () => void) {
|
|
|
2359
2351
|
* const double = derive(() => counter.value * 2);
|
|
2360
2352
|
*
|
|
2361
2353
|
* $('h3', () => {
|
|
2362
|
-
* $(
|
|
2354
|
+
* $(`#counter=${counter.value} double=${double.value}`);
|
|
2363
2355
|
* })
|
|
2364
2356
|
* ```
|
|
2365
2357
|
*
|
|
@@ -2367,7 +2359,7 @@ export function clean(cleaner: () => void) {
|
|
|
2367
2359
|
* @param func Func without a return value.
|
|
2368
2360
|
*/
|
|
2369
2361
|
export function derive<T>(func: () => T): ValueRef<T> {
|
|
2370
|
-
return new ResultScope<T>(
|
|
2362
|
+
return new ResultScope<T>(func).result;
|
|
2371
2363
|
}
|
|
2372
2364
|
|
|
2373
2365
|
/**
|
|
@@ -2399,12 +2391,12 @@ export function derive<T>(func: () => T): ValueRef<T> {
|
|
|
2399
2391
|
* setInterval(() => runTime.value++, 1000);
|
|
2400
2392
|
*
|
|
2401
2393
|
* mount(document.getElementById('app-root'), () => {
|
|
2402
|
-
* $('h4
|
|
2403
|
-
* $(`p
|
|
2394
|
+
* $('h4#Aberdeen App');
|
|
2395
|
+
* $(`p#Run time: ${runTime.value}s`);
|
|
2404
2396
|
* // Conditionally render some content somewhere else in the static page
|
|
2405
2397
|
* if (runTime.value&1) {
|
|
2406
2398
|
* mount(document.getElementById('title-extra'), () =>
|
|
2407
|
-
* $(`i
|
|
2399
|
+
* $(`i#(${runTime.value}s)`)
|
|
2408
2400
|
* );
|
|
2409
2401
|
* }
|
|
2410
2402
|
* });
|
|
@@ -2737,7 +2729,7 @@ export function partition<
|
|
|
2737
2729
|
func: (value: IN_V, key: KeyToString<IN_K>) => undefined | OUT_K | OUT_K[],
|
|
2738
2730
|
): Record<OUT_K, Record<KeyToString<IN_K>, IN_V>> {
|
|
2739
2731
|
const unproxiedOut = {} as Record<OUT_K, Record<KeyToString<IN_K>, IN_V>>;
|
|
2740
|
-
const out =
|
|
2732
|
+
const out = optProxy(unproxiedOut);
|
|
2741
2733
|
onEach(source, (item: IN_V, key: KeyToString<IN_K>) => {
|
|
2742
2734
|
const rsp = func(item, key);
|
|
2743
2735
|
if (rsp != null) {
|
|
@@ -2779,7 +2771,7 @@ export function partition<
|
|
|
2779
2771
|
* items: ['a', 'b']
|
|
2780
2772
|
* });
|
|
2781
2773
|
*
|
|
2782
|
-
* $('h2
|
|
2774
|
+
* $('h2#Live State Dump');
|
|
2783
2775
|
* dump(state);
|
|
2784
2776
|
*
|
|
2785
2777
|
* // Change state later, the dump in the DOM will update
|
|
@@ -2788,24 +2780,22 @@ export function partition<
|
|
|
2788
2780
|
*/
|
|
2789
2781
|
export function dump<T>(data: T): T {
|
|
2790
2782
|
if (data && typeof data === "object") {
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
label = "<Map>";
|
|
2783
|
+
const name = data.constructor.name.toLowerCase() || "unknown object";
|
|
2784
|
+
$(`#<${name}>`);
|
|
2785
|
+
if (NO_COPY in data ) {
|
|
2786
|
+
$("# [NO_COPY]");
|
|
2796
2787
|
} else {
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
dump(value);
|
|
2788
|
+
$("ul", () => {
|
|
2789
|
+
onEach(data as any, (value, key) => {
|
|
2790
|
+
$("li", () => {
|
|
2791
|
+
$(`#${JSON.stringify(key)}: `);
|
|
2792
|
+
dump(value);
|
|
2793
|
+
});
|
|
2804
2794
|
});
|
|
2805
2795
|
});
|
|
2806
|
-
}
|
|
2807
|
-
} else {
|
|
2808
|
-
$(
|
|
2796
|
+
}
|
|
2797
|
+
} else if (data !== undefined) {
|
|
2798
|
+
$("#" + JSON.stringify(data));
|
|
2809
2799
|
}
|
|
2810
2800
|
return data;
|
|
2811
2801
|
}
|
|
@@ -2827,7 +2817,7 @@ function handleError(e: any, showMessage: boolean) {
|
|
|
2827
2817
|
console.error(e);
|
|
2828
2818
|
}
|
|
2829
2819
|
try {
|
|
2830
|
-
if (showMessage) $("div.aberdeen-error
|
|
2820
|
+
if (showMessage) $("div.aberdeen-error#Error");
|
|
2831
2821
|
} catch {
|
|
2832
2822
|
// Error while adding the error marker to the DOM. Apparently, we're in
|
|
2833
2823
|
// an awkward context. The error should already have been logged by
|