aberdeen 1.1.0 → 1.2.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 +13 -4
- package/dist/aberdeen.d.ts +22 -5
- package/dist/aberdeen.js +99 -51
- 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/package.json +5 -5
- package/src/aberdeen.ts +135 -62
- package/src/route.ts +5 -4
package/README.md
CHANGED
|
@@ -31,14 +31,14 @@ const state = proxy({question: "How many roads must a man walk down?", answer: 4
|
|
|
31
31
|
|
|
32
32
|
$('h3', () => {
|
|
33
33
|
// This function reruns whenever the question or the answer changes
|
|
34
|
-
$(
|
|
34
|
+
$('text=', `${state.question} ↪ ${state.answer || 'Blowing in the wind'}`)
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
// Two-way bind state.question to an <input>
|
|
38
|
-
$('input
|
|
38
|
+
$('input placeholder=Question bind=', ref(state, 'question'))
|
|
39
39
|
|
|
40
40
|
// Allow state.answer to be modified using both an <input> and buttons
|
|
41
|
-
$('div.row
|
|
41
|
+
$('div.row $marginTop=1em', () => {
|
|
42
42
|
$('button:-', {click: () => state.answer--});
|
|
43
43
|
$('input', {type: 'number', bind: ref(state, 'answer')})
|
|
44
44
|
$('button:+', {click: () => state.answer++});
|
|
@@ -174,7 +174,16 @@ And you may want to study the examples above, of course!
|
|
|
174
174
|
|
|
175
175
|
## Changelog
|
|
176
176
|
|
|
177
|
-
### 1.
|
|
177
|
+
### 1.2.0 (2025-09-27)
|
|
178
|
+
|
|
179
|
+
**Enhancements:**
|
|
180
|
+
- The `$` function now supports a more concise syntax for setting attributes and properties. Instead of writing `$('p', 'button', {$color: 'red', click: () => ...})`, you can now write `$('p button $color=red click=', () => ...)`.
|
|
181
|
+
- The `proxy()` function can now accept `Promise`s, which will return an observable object with properties for `busy` status, `error` (if any), and the resolved `value`. This makes it easier to call async functions from within UI code.
|
|
182
|
+
|
|
183
|
+
**Breaking changes:**
|
|
184
|
+
- When a UI render function returns a `Promise`, that will now be reported as an error. Async render functions are fundamentally incompatible with Aberdeen's reactive model, so it's helpful to point that out. Use the new `proxy()` async support instead.
|
|
185
|
+
|
|
186
|
+
### 1.1.0 (2025-09-12)
|
|
178
187
|
|
|
179
188
|
This major release aims to reduce surprises in our API, aligning more closely with regular JavaScript semantics (for better or worse).
|
|
180
189
|
|
package/dist/aberdeen.d.ts
CHANGED
|
@@ -125,6 +125,12 @@ export interface ValueRef<T> {
|
|
|
125
125
|
* ```
|
|
126
126
|
*/
|
|
127
127
|
export declare function count(proxied: TargetType): ValueRef<number>;
|
|
128
|
+
interface PromiseProxy<T> {
|
|
129
|
+
busy: boolean;
|
|
130
|
+
error?: any;
|
|
131
|
+
value?: T;
|
|
132
|
+
}
|
|
133
|
+
export declare function proxy<T extends any>(target: Promise<T>): PromiseProxy<T>;
|
|
128
134
|
export declare function proxy<T extends any>(target: Array<T>): Array<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T>;
|
|
129
135
|
export declare function proxy<T extends object>(target: T): T;
|
|
130
136
|
export declare function proxy<T extends any>(target: T): ValueRef<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T>;
|
|
@@ -279,11 +285,13 @@ export declare function ref<T extends TargetType, K extends keyof T>(target: T,
|
|
|
279
285
|
* @param {...(string | function | object | false | undefined | null)} args - Any number of arguments can be given. How they're interpreted depends on their types:
|
|
280
286
|
*
|
|
281
287
|
* - `string`: Strings can be used to create and insert new elements, set classnames for the *current* element, and add text to the current element.
|
|
282
|
-
* The format of a string is: **tag
|
|
283
|
-
*
|
|
284
|
-
* -
|
|
285
|
-
* - Any number of CSS classes prefixed by `.` characters. These classes will be added to the *current* element.
|
|
286
|
-
* -
|
|
288
|
+
* The format of a string is: (**tag** | `.` **class** | **key**=**val** | **key**="**long val**")* (':' **text** | **key**=)?
|
|
289
|
+
* So there can be:
|
|
290
|
+
* - 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.
|
|
291
|
+
* - 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.
|
|
292
|
+
* - 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).
|
|
293
|
+
* - 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.
|
|
294
|
+
* - 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")`.
|
|
287
295
|
* - `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}).
|
|
288
296
|
* - `object`: When an object is passed in, its key-value pairs are used to modify the *current* element in the following ways...
|
|
289
297
|
* - `{<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.
|
|
@@ -318,6 +326,14 @@ export declare function ref<T extends TargetType, K extends keyof T>(target: T,
|
|
|
318
326
|
* });
|
|
319
327
|
* ```
|
|
320
328
|
*
|
|
329
|
+
* Which can also be written as:
|
|
330
|
+
* ```typescript
|
|
331
|
+
* $('button.secondary.outline text=Submit $color=red disabled=', false, 'click=', () => console.log('Clicked!'));
|
|
332
|
+
* ```
|
|
333
|
+
*
|
|
334
|
+
* We want to set `disabled` as a property instead of an attribute, so we must use the `key=` syntax in order to provide
|
|
335
|
+
* `false` as a boolean instead of a string.
|
|
336
|
+
*
|
|
321
337
|
* @example Create Nested Elements
|
|
322
338
|
* ```typescript
|
|
323
339
|
* let inputElement: Element = $('label:Click me', 'input', {type: 'checkbox'});
|
|
@@ -709,3 +725,4 @@ export declare function partition<IN_K extends string | number | symbol, OUT_K e
|
|
|
709
725
|
* ```
|
|
710
726
|
*/
|
|
711
727
|
export declare function dump<T>(data: T): T;
|
|
728
|
+
export {};
|
package/dist/aberdeen.js
CHANGED
|
@@ -645,7 +645,7 @@ var objectHandler = {
|
|
|
645
645
|
return true;
|
|
646
646
|
},
|
|
647
647
|
deleteProperty(target, prop) {
|
|
648
|
-
const old = target.hasOwnProperty(prop) ? target[prop] :
|
|
648
|
+
const old = target.hasOwnProperty(prop) ? target[prop] : EMPTY;
|
|
649
649
|
delete target[prop];
|
|
650
650
|
emit(target, prop, EMPTY, old);
|
|
651
651
|
return true;
|
|
@@ -862,6 +862,19 @@ function optProxy(value) {
|
|
|
862
862
|
return proxied;
|
|
863
863
|
}
|
|
864
864
|
function proxy(target) {
|
|
865
|
+
if (target instanceof Promise) {
|
|
866
|
+
const result = optProxy({
|
|
867
|
+
busy: true
|
|
868
|
+
});
|
|
869
|
+
target.then((value) => {
|
|
870
|
+
result.value = value;
|
|
871
|
+
result.busy = false;
|
|
872
|
+
}).catch((err) => {
|
|
873
|
+
result.error = err;
|
|
874
|
+
result.busy = false;
|
|
875
|
+
});
|
|
876
|
+
return result;
|
|
877
|
+
}
|
|
865
878
|
return optProxy(typeof target === "object" && target !== null ? target : { value: target });
|
|
866
879
|
}
|
|
867
880
|
function unproxy(target) {
|
|
@@ -876,14 +889,14 @@ function destroyWithClass(element, cls) {
|
|
|
876
889
|
function copy(a, b, c) {
|
|
877
890
|
if (arguments.length > 2)
|
|
878
891
|
return copySet(a, b, c, 0);
|
|
879
|
-
return
|
|
892
|
+
return copyImpl(a, b, 0);
|
|
880
893
|
}
|
|
881
894
|
function copySet(dst, dstKey, src, flags) {
|
|
882
895
|
let dstVal = peek(dst, dstKey);
|
|
883
896
|
if (src === dstVal)
|
|
884
897
|
return false;
|
|
885
898
|
if (typeof dstVal === "object" && dstVal && typeof src === "object" && src && dstVal.constructor === src.constructor) {
|
|
886
|
-
return
|
|
899
|
+
return copyImpl(dstVal, src, flags);
|
|
887
900
|
}
|
|
888
901
|
src = clone(src);
|
|
889
902
|
if (dst instanceof Map)
|
|
@@ -895,9 +908,9 @@ function copySet(dst, dstKey, src, flags) {
|
|
|
895
908
|
function merge(a, b, c) {
|
|
896
909
|
if (arguments.length > 2)
|
|
897
910
|
return copySet(a, b, c, MERGE);
|
|
898
|
-
return
|
|
911
|
+
return copyImpl(a, b, MERGE);
|
|
899
912
|
}
|
|
900
|
-
function
|
|
913
|
+
function copyImpl(dst, src, flags) {
|
|
901
914
|
let unproxied = dst[TARGET_SYMBOL];
|
|
902
915
|
if (unproxied) {
|
|
903
916
|
dst = unproxied;
|
|
@@ -909,6 +922,9 @@ function copyRecursive(dst, src, flags) {
|
|
|
909
922
|
if (currentScope !== ROOT_SCOPE && !peeking)
|
|
910
923
|
flags |= COPY_SUBSCRIBE;
|
|
911
924
|
}
|
|
925
|
+
return copyRecursive(dst, src, flags);
|
|
926
|
+
}
|
|
927
|
+
function copyRecursive(dst, src, flags) {
|
|
912
928
|
if (flags & COPY_SUBSCRIBE)
|
|
913
929
|
subscribe(src, ANY_SYMBOL);
|
|
914
930
|
let changed = false;
|
|
@@ -1025,7 +1041,7 @@ var COPY_SUBSCRIBE = 32;
|
|
|
1025
1041
|
var COPY_EMIT = 64;
|
|
1026
1042
|
function clone(src) {
|
|
1027
1043
|
const copied = Array.isArray(src) ? [] : src instanceof Map ? new Map : Object.create(Object.getPrototypeOf(src));
|
|
1028
|
-
|
|
1044
|
+
copyImpl(copied, src, MERGE);
|
|
1029
1045
|
return copied;
|
|
1030
1046
|
}
|
|
1031
1047
|
var refHandler = {
|
|
@@ -1124,23 +1140,80 @@ function $(...args) {
|
|
|
1124
1140
|
let savedCurrentScope;
|
|
1125
1141
|
let err;
|
|
1126
1142
|
let result;
|
|
1143
|
+
let nextArgIsProp;
|
|
1127
1144
|
for (let arg of args) {
|
|
1128
|
-
if (
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
let
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1145
|
+
if (nextArgIsProp) {
|
|
1146
|
+
applyArg(nextArgIsProp, arg);
|
|
1147
|
+
nextArgIsProp = undefined;
|
|
1148
|
+
} else if (arg == null || arg === false) {} else if (typeof arg === "string") {
|
|
1149
|
+
let pos = 0;
|
|
1150
|
+
let argLen = arg.length;
|
|
1151
|
+
while (pos < argLen) {
|
|
1152
|
+
let nextSpace = arg.indexOf(" ", pos);
|
|
1153
|
+
if (nextSpace < 0)
|
|
1154
|
+
nextSpace = arg.length;
|
|
1155
|
+
let part = arg.substring(pos, nextSpace);
|
|
1156
|
+
const oldPos = pos;
|
|
1157
|
+
pos = nextSpace + 1;
|
|
1158
|
+
const firstIs = part.indexOf("=");
|
|
1159
|
+
const firstColon = part.indexOf(":");
|
|
1160
|
+
if (firstIs >= 0 && (firstColon < 0 || firstIs < firstColon)) {
|
|
1161
|
+
const prop = part.substring(0, firstIs);
|
|
1162
|
+
if (firstIs < part.length - 1) {
|
|
1163
|
+
let value = part.substring(firstIs + 1);
|
|
1164
|
+
if (value[0] === '"') {
|
|
1165
|
+
const closeIndex = arg.indexOf('"', firstIs + 2 + oldPos);
|
|
1166
|
+
if (closeIndex < 0)
|
|
1167
|
+
throw new Error(`Unterminated string for '${prop}'`);
|
|
1168
|
+
value = arg.substring(firstIs + 2 + oldPos, closeIndex);
|
|
1169
|
+
pos = closeIndex + 1;
|
|
1170
|
+
if (arg[pos] === " ")
|
|
1171
|
+
pos++;
|
|
1172
|
+
}
|
|
1173
|
+
applyArg(prop, value);
|
|
1174
|
+
continue;
|
|
1175
|
+
} else {
|
|
1176
|
+
if (pos < argLen)
|
|
1177
|
+
throw new Error(`No value given for '${part}'`);
|
|
1178
|
+
nextArgIsProp = prop;
|
|
1179
|
+
break;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
let text;
|
|
1183
|
+
if (firstColon >= 0) {
|
|
1184
|
+
text = arg.substring(firstColon + 1 + oldPos);
|
|
1185
|
+
part = part.substring(0, firstColon);
|
|
1186
|
+
if (!text) {
|
|
1187
|
+
if (pos < argLen)
|
|
1188
|
+
throw new Error(`No value given for '${part}'`);
|
|
1189
|
+
nextArgIsProp = "text";
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
pos = argLen;
|
|
1193
|
+
}
|
|
1194
|
+
let classes;
|
|
1195
|
+
const classPos = part.indexOf(".");
|
|
1196
|
+
if (classPos >= 0) {
|
|
1197
|
+
classes = part.substring(classPos + 1);
|
|
1198
|
+
part = part.substring(0, classPos);
|
|
1199
|
+
}
|
|
1200
|
+
if (part) {
|
|
1201
|
+
const svg = currentScope.inSvgNamespace || part === "svg";
|
|
1202
|
+
if (svg) {
|
|
1203
|
+
result = document.createElementNS("http://www.w3.org/2000/svg", part);
|
|
1204
|
+
} else {
|
|
1205
|
+
result = document.createElement(part);
|
|
1206
|
+
}
|
|
1207
|
+
addNode(result);
|
|
1208
|
+
if (!savedCurrentScope)
|
|
1209
|
+
savedCurrentScope = currentScope;
|
|
1210
|
+
const newScope = new ChainedScope(result, true);
|
|
1211
|
+
if (svg)
|
|
1212
|
+
newScope.inSvgNamespace = true;
|
|
1213
|
+
if (topRedrawScope === currentScope)
|
|
1214
|
+
topRedrawScope = newScope;
|
|
1215
|
+
currentScope = newScope;
|
|
1216
|
+
}
|
|
1144
1217
|
if (text)
|
|
1145
1218
|
addNode(document.createTextNode(text));
|
|
1146
1219
|
if (classes) {
|
|
@@ -1150,32 +1223,6 @@ function $(...args) {
|
|
|
1150
1223
|
clean(() => el.classList.remove(...classes.split(".")));
|
|
1151
1224
|
}
|
|
1152
1225
|
}
|
|
1153
|
-
} else if (arg.indexOf(" ") >= 0) {
|
|
1154
|
-
err = `Tag '${arg}' cannot contain space`;
|
|
1155
|
-
break;
|
|
1156
|
-
} else {
|
|
1157
|
-
const useNamespace = currentScope.inSvgNamespace || arg === "svg";
|
|
1158
|
-
if (useNamespace) {
|
|
1159
|
-
result = document.createElementNS("http://www.w3.org/2000/svg", arg);
|
|
1160
|
-
} else {
|
|
1161
|
-
result = document.createElement(arg);
|
|
1162
|
-
}
|
|
1163
|
-
if (classes)
|
|
1164
|
-
result.className = classes.replaceAll(".", " ");
|
|
1165
|
-
if (text)
|
|
1166
|
-
result.textContent = text;
|
|
1167
|
-
addNode(result);
|
|
1168
|
-
if (!savedCurrentScope) {
|
|
1169
|
-
savedCurrentScope = currentScope;
|
|
1170
|
-
}
|
|
1171
|
-
const newScope = new ChainedScope(result, true);
|
|
1172
|
-
if (arg === "svg") {
|
|
1173
|
-
newScope.inSvgNamespace = true;
|
|
1174
|
-
}
|
|
1175
|
-
newScope.lastChild = result.lastChild || undefined;
|
|
1176
|
-
if (topRedrawScope === currentScope)
|
|
1177
|
-
topRedrawScope = newScope;
|
|
1178
|
-
currentScope = newScope;
|
|
1179
1226
|
}
|
|
1180
1227
|
} else if (typeof arg === "object") {
|
|
1181
1228
|
if (arg.constructor !== Object) {
|
|
@@ -1204,9 +1251,10 @@ function $(...args) {
|
|
|
1204
1251
|
break;
|
|
1205
1252
|
}
|
|
1206
1253
|
}
|
|
1207
|
-
if (
|
|
1254
|
+
if (nextArgIsProp !== undefined)
|
|
1255
|
+
throw new Error(`No value given for '${nextArgIsProp}='`);
|
|
1256
|
+
if (savedCurrentScope)
|
|
1208
1257
|
currentScope = savedCurrentScope;
|
|
1209
|
-
}
|
|
1210
1258
|
if (err)
|
|
1211
1259
|
throw new Error(err);
|
|
1212
1260
|
return result;
|
|
@@ -1455,5 +1503,5 @@ export {
|
|
|
1455
1503
|
$
|
|
1456
1504
|
};
|
|
1457
1505
|
|
|
1458
|
-
//# debugId=
|
|
1506
|
+
//# debugId=1793ACD02DE96EE964756E2164756E21
|
|
1459
1507
|
//# sourceMappingURL=aberdeen.js.map
|