aberdeen 1.17.0 → 2.0.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/dist/src/aberdeen.d.ts +928 -944
- package/dist/src/aberdeen.js +1693 -1704
- package/dist/src/aberdeen.js.map +3 -3
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +10 -0
- package/dist/src/prediction.d.ts +7 -4
- package/dist/src/prediction.js +18 -23
- package/dist/src/prediction.js.map +3 -3
- package/dist/src/route.d.ts +112 -99
- package/dist/src/route.js +254 -254
- package/dist/src/route.js.map +3 -3
- package/dist/src/server-dom.d.ts +158 -0
- package/dist/src/server-dom.js +458 -0
- package/dist/src/server-dom.js.map +10 -0
- package/dist/src/server.d.ts +55 -0
- package/dist/src/server.js +25 -0
- package/dist/src/server.js.map +10 -0
- package/dist/src/transitions.js +7 -4
- package/dist/src/transitions.js.map +3 -3
- package/dist/tests/environment.d.ts +24 -0
- package/dist/tests/{fakedom.js → environment.js} +239 -288
- package/dist/tests/environment.js.map +11 -0
- package/dist/tests/helpers.d.ts +15 -3
- package/dist/tests/helpers.js +277 -306
- package/dist/tests/helpers.js.map +6 -5
- package/dist-docs/Tutorial/index.html +30 -30
- package/dist-docs/aberdeen/Aberdeen/index.html +350 -0
- package/dist-docs/aberdeen/InitOptions/index.html +4 -0
- package/dist-docs/aberdeen/KeyToString/index.html +2 -0
- package/dist-docs/aberdeen/PromiseProxy/index.html +6 -6
- package/dist-docs/aberdeen/SortKeyType/index.html +2 -0
- package/dist-docs/aberdeen/TargetType/index.html +2 -0
- package/dist-docs/aberdeen/ValueRef/index.html +2 -0
- package/dist-docs/aberdeen/WindowLike/index.html +5 -0
- package/dist-docs/aberdeen/{clone → createA}/index.html +10 -5
- package/dist-docs/aberdeen/index.html +13 -54
- package/dist-docs/assets/aberdeen/aberdeen.d.ts +928 -944
- package/dist-docs/assets/aberdeen/aberdeen.js +1693 -1704
- package/dist-docs/assets/aberdeen/aberdeen.js.map +3 -3
- package/dist-docs/assets/aberdeen/index.d.ts +12 -0
- package/dist-docs/assets/aberdeen/index.js +11 -0
- package/dist-docs/assets/aberdeen/index.js.map +10 -0
- package/dist-docs/assets/aberdeen/prediction.d.ts +7 -4
- package/dist-docs/assets/aberdeen/prediction.js +18 -23
- package/dist-docs/assets/aberdeen/prediction.js.map +3 -3
- package/dist-docs/assets/aberdeen/route.d.ts +112 -99
- package/dist-docs/assets/aberdeen/route.js +254 -254
- package/dist-docs/assets/aberdeen/route.js.map +3 -3
- package/dist-docs/assets/aberdeen/server-dom.d.ts +158 -0
- package/dist-docs/assets/aberdeen/server-dom.js +458 -0
- package/dist-docs/assets/aberdeen/server-dom.js.map +10 -0
- package/dist-docs/assets/aberdeen/server.d.ts +55 -0
- package/dist-docs/assets/aberdeen/server.js +25 -0
- package/dist-docs/assets/aberdeen/server.js.map +10 -0
- package/dist-docs/assets/aberdeen/transitions.js +7 -4
- package/dist-docs/assets/aberdeen/transitions.js.map +3 -3
- package/dist-docs/assets/navigation.js +1 -1
- package/dist-docs/assets/search.js +1 -1
- package/dist-docs/dispatcher/Dispatcher/index.html +5 -6
- package/dist-docs/dispatcher/MATCH_FAILED/index.html +2 -2
- package/dist-docs/dispatcher/MATCH_REST/index.html +2 -2
- package/dist-docs/dispatcher/index.html +2 -2
- package/dist-docs/hierarchy.html +1 -1
- package/dist-docs/index-1/index.html +2 -0
- package/dist-docs/index.html +2 -2
- package/dist-docs/media/CHANGELOG.md +14 -2
- package/dist-docs/modules.html +1 -1
- package/dist-docs/prediction/applyCanon/index.html +4 -3
- package/dist-docs/prediction/applyPrediction/index.html +4 -3
- package/dist-docs/prediction/index.html +2 -2
- package/dist-docs/route/Route/index.html +9 -9
- package/dist-docs/route/RouteApi/index.html +60 -0
- package/dist-docs/route/index.html +6 -15
- package/dist-docs/route/init/index.html +5 -0
- package/dist-docs/server/RenderToStringOptions/index.html +6 -0
- package/dist-docs/server/RenderToStringResult/index.html +6 -0
- package/dist-docs/server/index.html +4 -0
- package/dist-docs/server/renderToString/index.html +6 -0
- package/dist-docs/sitemap.xml +51 -155
- package/dist-docs/transitions/grow/index.html +2 -2
- package/dist-docs/transitions/index.html +2 -2
- package/dist-docs/transitions/shrink/index.html +2 -2
- package/package.json +17 -7
- package/skill/SKILL.md +28 -28
- package/skill/aberdeen.md +471 -3448
- package/skill/dispatcher.md +6 -16
- package/skill/prediction.md +17 -5
- package/skill/route.md +95 -77
- package/skill/transitions.md +3 -3
- package/src/aberdeen.ts +1226 -740
- package/src/index.ts +13 -0
- package/src/prediction.ts +25 -24
- package/src/route.ts +192 -142
- package/src/server-dom.ts +508 -0
- package/src/server.ts +74 -0
- package/src/transitions.ts +9 -3
- package/dist/tests/fakedom.d.ts +0 -9
- package/dist/tests/fakedom.js.map +0 -10
- package/dist-docs/aberdeen/A/index.html +0 -115
- package/dist-docs/aberdeen/CUSTOM_DUMP/index.html +0 -7
- package/dist-docs/aberdeen/NO_COPY/index.html +0 -2
- package/dist-docs/aberdeen/OPAQUE/index.html +0 -12
- package/dist-docs/aberdeen/clean/index.html +0 -11
- package/dist-docs/aberdeen/copy/index.html +0 -22
- package/dist-docs/aberdeen/count/index.html +0 -7
- package/dist-docs/aberdeen/cssVars/index.html +0 -11
- package/dist-docs/aberdeen/darkMode/index.html +0 -9
- package/dist-docs/aberdeen/default/index.html +0 -341
- package/dist-docs/aberdeen/derive/index.html +0 -18
- package/dist-docs/aberdeen/disableCreateDestroy/index.html +0 -6
- package/dist-docs/aberdeen/dump/index.html +0 -11
- package/dist-docs/aberdeen/insertCss/index.html +0 -30
- package/dist-docs/aberdeen/insertGlobalCss/index.html +0 -19
- package/dist-docs/aberdeen/invertString/index.html +0 -13
- package/dist-docs/aberdeen/isEmpty/index.html +0 -12
- package/dist-docs/aberdeen/map/index.html +0 -26
- package/dist-docs/aberdeen/merge/index.html +0 -11
- package/dist-docs/aberdeen/mount/index.html +0 -17
- package/dist-docs/aberdeen/multiMap/index.html +0 -28
- package/dist-docs/aberdeen/onEach/index.html +0 -19
- package/dist-docs/aberdeen/partition/index.html +0 -36
- package/dist-docs/aberdeen/peek/index.html +0 -40
- package/dist-docs/aberdeen/proxy/index.html +0 -35
- package/dist-docs/aberdeen/ref/index.html +0 -13
- package/dist-docs/aberdeen/runQueue/index.html +0 -13
- package/dist-docs/aberdeen/setErrorHandler/index.html +0 -16
- package/dist-docs/aberdeen/setSpacingCssVars/index.html +0 -8
- package/dist-docs/aberdeen/unmountAll/index.html +0 -5
- package/dist-docs/aberdeen/unproxy/index.html +0 -11
- package/dist-docs/route/back/index.html +0 -4
- package/dist-docs/route/current/index.html +0 -2
- package/dist-docs/route/go/index.html +0 -14
- package/dist-docs/route/interceptLinks/index.html +0 -8
- package/dist-docs/route/matchCurrent/index.html +0 -10
- package/dist-docs/route/persistScroll/index.html +0 -6
- package/dist-docs/route/push/index.html +0 -6
- package/dist-docs/route/setLog/index.html +0 -3
- package/dist-docs/route/up/index.html +0 -5
package/src/aberdeen.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
|
-
import { ReverseSortedSet } from "./helpers/reverseSortedSet.js";
|
|
2
|
-
|
|
1
|
+
import { ReverseSortedSet, type ReverseSortedSetPointer } from "./helpers/reverseSortedSet.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The minimal `window`-like object an Aberdeen instance renders against. A real browser
|
|
5
|
+
* `window`, a jsdom window, and the server window from `aberdeen/server` all satisfy this.
|
|
6
|
+
*/
|
|
7
|
+
export interface WindowLike {
|
|
8
|
+
document: any;
|
|
9
|
+
Node: any;
|
|
10
|
+
Element: any;
|
|
11
|
+
matchMedia?: (query: string) => any;
|
|
12
|
+
location?: any;
|
|
13
|
+
history?: any;
|
|
14
|
+
addEventListener?: (...args: any[]) => void;
|
|
15
|
+
removeEventListener?: (...args: any[]) => void;
|
|
16
|
+
/** Optional; falls back to the host's timers when absent (e.g. the lean server window). */
|
|
17
|
+
setTimeout?: (...args: any[]) => any;
|
|
18
|
+
queueMicrotask?: (callback: () => void) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Options for {@link createA}. */
|
|
22
|
+
export interface InitOptions {
|
|
23
|
+
/** Set to `false` to disable `create`/`destroy` transitions for this instance (e.g. for SSR). */
|
|
24
|
+
transitions?: boolean;
|
|
25
|
+
}
|
|
3
26
|
|
|
4
27
|
/*
|
|
5
28
|
* QueueRunner
|
|
@@ -14,6 +37,1054 @@ interface QueueRunner {
|
|
|
14
37
|
[ptr: ReverseSortedSetPointer]: QueueRunner;
|
|
15
38
|
}
|
|
16
39
|
|
|
40
|
+
/** Any value Aberdeen can proxy: array, plain object, Map or Set. */
|
|
41
|
+
export type TargetType = any[] | { [key: string | symbol]: any } | Map<any, any> | Set<any>;
|
|
42
|
+
/** A sort key returned by `onEach`/`map` ordering callbacks. */
|
|
43
|
+
export type SortKeyType = number | string | Array<number | string> | undefined | void;
|
|
44
|
+
/** Maps object key types to the string form Aberdeen passes to callbacks. */
|
|
45
|
+
export type KeyToString<K> = K extends number ? string : K extends string | symbol ? K : K extends number | infer U ? string | U : K;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The Aberdeen instance returned by {@link createA}: a callable element-builder with every
|
|
49
|
+
* Aberdeen function attached as a method. `import A from 'aberdeen'` gives you one of these.
|
|
50
|
+
*/
|
|
51
|
+
export interface Aberdeen {
|
|
52
|
+
/** Build reactive DOM: create elements, set attributes/properties/event listeners, add content. See the tutorial for the full mini-language. */
|
|
53
|
+
(...args: any[]): undefined | Element;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Registers a cleanup function to be executed just before the current reactive scope
|
|
57
|
+
* is destroyed or redraws.
|
|
58
|
+
*
|
|
59
|
+
* This is useful for releasing resources, removing manual event listeners, or cleaning up
|
|
60
|
+
* side effects associated with the scope. Cleaners are run in reverse order of registration.
|
|
61
|
+
*
|
|
62
|
+
* Scopes are created by functions like {@link Aberdeen.derive}, {@link Aberdeen.mount}, {@link Aberdeen | A} (when given a render function),
|
|
63
|
+
* and internally by constructs like {@link Aberdeen.onEach}.
|
|
64
|
+
*
|
|
65
|
+
* @param cleaner - The function to execute during cleanup.
|
|
66
|
+
*
|
|
67
|
+
* @example Maintaing a sum for a changing array
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const $numbers = A.proxy([3, 5, 10]);
|
|
70
|
+
* let $sum = A.proxy(0);
|
|
71
|
+
*
|
|
72
|
+
* // Show the array items and maintain the sum
|
|
73
|
+
* A.onEach($numbers, (item, index) => {
|
|
74
|
+
* A(`code#${index}→${item}`);
|
|
75
|
+
* // We'll update $sum.value using peek, as += first does a read, but
|
|
76
|
+
* // we don't want to subscribe.
|
|
77
|
+
* A.peek(() => $sum.value += item);
|
|
78
|
+
* // Clean gets called before each rerun for a certain item index
|
|
79
|
+
* // No need for peek here, as the clean code doesn't run in an
|
|
80
|
+
* // observer scope.
|
|
81
|
+
* A.clean(() => $sum.value -= item);
|
|
82
|
+
* })
|
|
83
|
+
*
|
|
84
|
+
* // Show the sum
|
|
85
|
+
* A('h1 text=', $sum);
|
|
86
|
+
*
|
|
87
|
+
* // Make random changes to the array
|
|
88
|
+
* const rnd = () => 0|(Math.random()*20);
|
|
89
|
+
* setInterval(() => $numbers[rnd()] = rnd(), 1000);
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
clean(cleaner: () => void): void;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Clone an (optionally proxied) object or array.
|
|
96
|
+
*
|
|
97
|
+
* @param src The object or array to clone. If it is proxied, `clone` will subscribe to any changes to the (nested) data structure.
|
|
98
|
+
* @template T - The type of the objects being copied.
|
|
99
|
+
* @returns A new unproxied array or object (of the same type as `src`), containing a deep copy of `src`.
|
|
100
|
+
*/
|
|
101
|
+
clone<T extends object>(src: T): T;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Recursively copies properties or array items from `src` to `dst`.
|
|
105
|
+
* It's designed to work efficiently with reactive proxies created by {@link Aberdeen.proxy}.
|
|
106
|
+
*
|
|
107
|
+
* - **Minimizes Updates:** When copying between objects/arrays (proxied or not), if a nested object
|
|
108
|
+
* exists in `dst` with the same constructor as the corresponding object in `src`, `copy`
|
|
109
|
+
* will recursively copy properties into the existing `dst` object instead of replacing it.
|
|
110
|
+
* This minimizes change notifications for reactive (proxied) destinations.
|
|
111
|
+
* - **Fast with Proxies:** When copying to/from proxied objects, `copy` uses Aberdeen internals
|
|
112
|
+
* to speed things up (compared to a non-Aberdeen-aware deep copy).
|
|
113
|
+
*
|
|
114
|
+
* @param dst - The destination object/array/Map (proxied or unproxied).
|
|
115
|
+
* @param src - The source object/array/Map (proxied or unproxied). It won't be modified.
|
|
116
|
+
* @template T - The type of the objects being copied.
|
|
117
|
+
* @returns `true` if any changes were made to `dst`, or `false` if not.
|
|
118
|
+
* @throws Error if attempting to copy an array into a non-array or vice versa.
|
|
119
|
+
*
|
|
120
|
+
* @example Basic Copy
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const $source = A.proxy({ a: 1, b: { c: 2 } });
|
|
123
|
+
* const $dest = A.proxy({ b: { d: 3 } });
|
|
124
|
+
* A.copy($dest, $source);
|
|
125
|
+
* console.log($dest); // proxy({ a: 1, b: { c: 2 } })
|
|
126
|
+
* A.copy($dest, 'b', { e: 4 });
|
|
127
|
+
* console.log($dest); // proxy({ a: 1, b: { e: 4 } })
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
copy<T extends object>(dst: T, src: T): boolean;
|
|
131
|
+
copy<T extends object>(dst: T, dstKey: keyof T, src: T[typeof dstKey]): boolean;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Reactively counts the number of properties in an object.
|
|
135
|
+
*
|
|
136
|
+
* @param proxied The observable object to count. In case an `array`, `Map`, or `Set` is passed in, a {@link Aberdeen.ref} to its `.length` or `.size` will be returned.
|
|
137
|
+
* @returns an observable object for which the `value` property reflects the number of properties in `proxied` with a value other than `undefined`, or the collection size for arrays, Maps, and Sets.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const $items = A.proxy({x: 3, y: 7} as any);
|
|
142
|
+
* const $count = A.count($items);
|
|
143
|
+
*
|
|
144
|
+
* // Create a DOM text node for the count:
|
|
145
|
+
* A('div text=', $count);
|
|
146
|
+
* // <div>2</div>
|
|
147
|
+
|
|
148
|
+
* // Or we can use it in an {@link Aberdeen.derive} function:
|
|
149
|
+
* A(() => console.log("The count is now", $count.value));
|
|
150
|
+
* // The count is now 2
|
|
151
|
+
*
|
|
152
|
+
* // Adding/removing items will update the count
|
|
153
|
+
* $items.z = 12;
|
|
154
|
+
* // Asynchronously, after 0ms:
|
|
155
|
+
* // <div>3</div>
|
|
156
|
+
* // The count is now 3
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
count(proxied: TargetType): ValueRef<number>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* A reactive object containing CSS variable definitions.
|
|
163
|
+
*
|
|
164
|
+
* Any property you assign to `cssVars` becomes available as a CSS custom property throughout your application.
|
|
165
|
+
*
|
|
166
|
+
* Use {@link Aberdeen.setSpacingCssVars} to optionally initialize `cssVars[1]` through `cssVars[12]` with an exponential spacing scale.
|
|
167
|
+
*
|
|
168
|
+
* When you reference a CSS variable in Aberdeen using the `$` prefix (e.g., `$primary`), it automatically resolves to `var(--primary)`.
|
|
169
|
+
* For numeric keys (which can't be used directly as CSS custom property names), Aberdeen prefixes them with `m` (e.g., `$3` becomes `var(--m3)`).
|
|
170
|
+
*
|
|
171
|
+
* When you add the first property to cssVars, Aberdeen automatically creates a reactive `<style>` tag in `<head>`
|
|
172
|
+
* containing the `:root` CSS custom property declarations. The style tag is automatically removed if cssVars becomes empty.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```javascript
|
|
176
|
+
* import A from 'aberdeen';
|
|
177
|
+
*
|
|
178
|
+
* // Optionally initialize spacing scale
|
|
179
|
+
* A.setSpacingCssVars(); // Uses defaults: base=1, unit='rem'
|
|
180
|
+
*
|
|
181
|
+
* // Define custom colors - style tag is automatically created
|
|
182
|
+
* A.cssVars.primary = '#3b82f6';
|
|
183
|
+
* A.cssVars.danger = '#ef4444';
|
|
184
|
+
*
|
|
185
|
+
* // Use in elements with the $ prefix
|
|
186
|
+
* A('button bg:$primary fg:white #I'm a primary button');
|
|
187
|
+
*
|
|
188
|
+
* // Use spacing (if setSpacingCssVars() was called)
|
|
189
|
+
* A('hr mt:$3'); // Sets margin-top to var(--m3)
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
cssVars: Record<string, string>;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* When set on an object or its prototype chain, {@link Aberdeen.dump} calls this as a render function
|
|
196
|
+
* (with the object as `this`) instead of its default recursive rendering. If the value is not
|
|
197
|
+
* a function, it's treated as a string to display.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* class Color {
|
|
202
|
+
* constructor(public r: number, public g: number, public b: number) {}
|
|
203
|
+
* [CUSTOM_DUMP]() { A(`#rgb(${this.r}, ${this.g}, ${this.b})`); }
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
CUSTOM_DUMP: any;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Returns whether the user's browser prefers a dark color scheme.
|
|
211
|
+
*
|
|
212
|
+
* This function is reactive - scopes that call it will re-execute when the
|
|
213
|
+
* browser's color scheme preference changes (via the `prefers-color-scheme` media query).
|
|
214
|
+
*
|
|
215
|
+
* Use this in combination with {@link Aberdeen | A} and {@link Aberdeen.cssVars} to implement theme switching:
|
|
216
|
+
*
|
|
217
|
+
* @returns `true` if the browser prefers dark mode, `false` if it prefers light mode.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```javascript
|
|
221
|
+
* import A from 'aberdeen';
|
|
222
|
+
*
|
|
223
|
+
* // Reactively set colors based on browser preference
|
|
224
|
+
* A(() => {
|
|
225
|
+
* A.cssVars.bg = A.darkMode() ? '#1a1a1a' : '#ffffff';
|
|
226
|
+
* A.cssVars.fg = A.darkMode() ? '#e5e5e5' : '#000000';
|
|
227
|
+
* });
|
|
228
|
+
*
|
|
229
|
+
* A('div bg:$bg fg:$fg p:1rem #Colors change based on system dark mode preference');
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
darkMode(): boolean;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Creates a reactive scope that automatically re-executes the provided function
|
|
236
|
+
* whenever any proxied data (created by {@link Aberdeen.proxy}) read during its last execution changes, storing
|
|
237
|
+
* its return value in an observable.
|
|
238
|
+
*
|
|
239
|
+
* Updates are batched and run asynchronously shortly after the changes occur.
|
|
240
|
+
* Use {@link Aberdeen.clean} to register cleanup logic for the scope.
|
|
241
|
+
* Use {@link Aberdeen.peek} or {@link Aberdeen.unproxy} within the function to read proxied data without subscribing to it.
|
|
242
|
+
*
|
|
243
|
+
* @param func - The function to execute reactively. Any DOM manipulations should typically
|
|
244
|
+
* be done using {@link Aberdeen | A} within this function. Its return value will be made available as an
|
|
245
|
+
* observable returned by the `derive()` function.
|
|
246
|
+
* @returns An observable object, with its `value` property containing whatever the last run of `func` returned.
|
|
247
|
+
*
|
|
248
|
+
* @example Observation creating UI components
|
|
249
|
+
* ```typescript
|
|
250
|
+
* const $data = A.proxy({ user: 'Frank', notifications: 42 });
|
|
251
|
+
*
|
|
252
|
+
* A('main', () => {
|
|
253
|
+
* console.log('Welcome');
|
|
254
|
+
* A('h3#Welcome, ' + $data.user); // Reactive text
|
|
255
|
+
*
|
|
256
|
+
* A.derive(() => {
|
|
257
|
+
* // When $data.notifications changes, only this inner scope reruns,
|
|
258
|
+
* // leaving the `<p>Welcome, ..</p>` untouched.
|
|
259
|
+
* console.log('Notifications');
|
|
260
|
+
* A('code.notification-badge text=', $data.notifications);
|
|
261
|
+
* A('a text=Notify! click=', () => $data.notifications++);
|
|
262
|
+
* });
|
|
263
|
+
* });
|
|
264
|
+
* ```
|
|
265
|
+
*
|
|
266
|
+
* ***Note*** that the above could just as easily be done using `A(func)` instead of `derive(func)`.
|
|
267
|
+
*
|
|
268
|
+
* @example Observation with return value
|
|
269
|
+
* ```typescript
|
|
270
|
+
* const $counter = A.proxy(0);
|
|
271
|
+
* setInterval(() => $counter.value++, 1000);
|
|
272
|
+
* const $double = A.derive(() => $counter.value * 2);
|
|
273
|
+
*
|
|
274
|
+
* A('h3', () => {
|
|
275
|
+
* A(`#counter=${$counter.value} double=${$double.value}`);
|
|
276
|
+
* })
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* @overload
|
|
280
|
+
* @param func Func without a return value.
|
|
281
|
+
*/
|
|
282
|
+
derive<T>(func: () => T): ValueRef<T>;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Renders a live, recursive dump of a proxied data structure (or any value)
|
|
286
|
+
* into the DOM at the current {@link Aberdeen | A} insertion point.
|
|
287
|
+
*
|
|
288
|
+
* Uses `<ul>` and `<li>` elements to display object properties and array items.
|
|
289
|
+
* Updates reactively if the dumped data changes. Primarily intended for debugging purposes.
|
|
290
|
+
*
|
|
291
|
+
* @param data - The proxied data structure (or any value) to display.
|
|
292
|
+
* @returns The original `data` argument, allowing for chaining.
|
|
293
|
+
* @template T - The type of the data being dumped.
|
|
294
|
+
*
|
|
295
|
+
* @example Dumping reactive state
|
|
296
|
+
* ```typescript
|
|
297
|
+
* import A from 'aberdeen';
|
|
298
|
+
*
|
|
299
|
+
* const $state = A.proxy({
|
|
300
|
+
* user: { name: 'Frank', kids: 1 },
|
|
301
|
+
* items: ['a', 'b']
|
|
302
|
+
* });
|
|
303
|
+
*
|
|
304
|
+
* A('h2#Live State Dump');
|
|
305
|
+
* A.dump($state);
|
|
306
|
+
*
|
|
307
|
+
* // Change state later, the dump in the DOM will update
|
|
308
|
+
* setTimeout(() => { $state.user.kids++; $state.items.push('c'); }, 2000);
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
dump<T>(data: T): T;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Inserts CSS rules into the document, scoping them with a unique class name.
|
|
315
|
+
*
|
|
316
|
+
* The `style` parameter can be either:
|
|
317
|
+
* - A **concise style string** (for rules applying to the root class).
|
|
318
|
+
* - An **object** where keys are selectors (with `&` representing the root class)
|
|
319
|
+
* and values are concise style strings or nested objects. When the key does not contain `&`,
|
|
320
|
+
* it is treated as a descendant selector. So `{p: "color:red"}` becomes `".AbdStlX p { color: red; }"` with `AbdStlX` being the generated class name.
|
|
321
|
+
*
|
|
322
|
+
* ### Concise Style Strings
|
|
323
|
+
*
|
|
324
|
+
* Concise style strings use two syntaxes (same as inline CSS in {@link Aberdeen | A}):
|
|
325
|
+
* - **Short form** `key:value` (no space after colon): The value ends at the next whitespace.
|
|
326
|
+
* Example: `'m:$3 bg:red r:8px'`
|
|
327
|
+
* - **Long form** `key: value;` (space after colon): The value continues until a semicolon.
|
|
328
|
+
* Example: `'box-shadow: 2px 0 6px black; transition: all 0.3s ease;'`
|
|
329
|
+
*
|
|
330
|
+
* Both forms can be mixed: `'m:$3 box-shadow: 0 2px 4px rgba(0,0,0,0.2); bg:$cardBg'`
|
|
331
|
+
*
|
|
332
|
+
* Supports the same CSS shortcuts as {@link Aberdeen | A} and CSS variable references with `$` (e.g., `$primary`, `$3`).
|
|
333
|
+
*
|
|
334
|
+
* @param style - A concise style string or a style object.
|
|
335
|
+
* @returns The unique class name prefix used for scoping (e.g., `.AbdStl1`).
|
|
336
|
+
* Use this prefix with {@link Aberdeen | A} to apply the styles.
|
|
337
|
+
*
|
|
338
|
+
* @example Basic Usage with Shortcuts and CSS Variables
|
|
339
|
+
* ```typescript
|
|
340
|
+
* const cardClass = A.insertCss({
|
|
341
|
+
* '&': 'bg:white p:$4 r:8px transition: background-color 0.3s;',
|
|
342
|
+
* '&:hover': 'bg:#f5f5f5',
|
|
343
|
+
* });
|
|
344
|
+
*
|
|
345
|
+
* A('section', cardClass, () => {
|
|
346
|
+
* A('p#Card content');
|
|
347
|
+
* });
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* @example Nested Selectors and Media Queries
|
|
351
|
+
* ```typescript
|
|
352
|
+
* const formClass = A.insertCss({
|
|
353
|
+
* '&': 'bg:#0004 p:$3 r:$2',
|
|
354
|
+
* button: {
|
|
355
|
+
* '&': 'bg:$primary fg:white p:$2 r:4px cursor:pointer',
|
|
356
|
+
* '&:hover': 'bg:$primaryHover',
|
|
357
|
+
* '&:disabled': 'bg:#ccc cursor:not-allowed',
|
|
358
|
+
* '.icon': 'display:inline-block mr:$1',
|
|
359
|
+
* '@media (max-width: 600px)': 'p:$1 font-size:14px'
|
|
360
|
+
* }
|
|
361
|
+
* });
|
|
362
|
+
*
|
|
363
|
+
* A('form', formClass, () => {
|
|
364
|
+
* A('button', () => {
|
|
365
|
+
* A('span.icon text=🔥');
|
|
366
|
+
* A('#Click Me');
|
|
367
|
+
* });
|
|
368
|
+
* });
|
|
369
|
+
* ```
|
|
370
|
+
*
|
|
371
|
+
* @example Complex CSS Values
|
|
372
|
+
* ```typescript
|
|
373
|
+
* const badge = A.insertCss({
|
|
374
|
+
* '&::before': 'content: "★"; color:gold mr:$1',
|
|
375
|
+
* '&': 'position:relative box-shadow: 0 2px 8px rgba(0,0,0,0.15);'
|
|
376
|
+
* });
|
|
377
|
+
*
|
|
378
|
+
* A(badge + ' span#Product Name');
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
insertCss(style: string | object): string;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Inserts CSS rules globally (unscoped).
|
|
385
|
+
*
|
|
386
|
+
* Works exactly like {@link Aberdeen.insertCss}, but without prefixing selectors with a unique class name.
|
|
387
|
+
* This is useful for global resets, base styles, or styles that need to apply to the entire document.
|
|
388
|
+
*
|
|
389
|
+
* Accepts the same concise style string syntax and CSS shortcuts as {@link Aberdeen.insertCss}.
|
|
390
|
+
* See {@link Aberdeen.insertCss} for detailed documentation on syntax and shortcuts.
|
|
391
|
+
*
|
|
392
|
+
* @param style - Object with selectors as keys and concise CSS strings as values.
|
|
393
|
+
*
|
|
394
|
+
* @example Global Reset and Base Styles
|
|
395
|
+
* ```typescript
|
|
396
|
+
* // Set up global styles using CSS shortcuts
|
|
397
|
+
* A.insertGlobalCss({
|
|
398
|
+
* "*": "m:0 p:0 box-sizing:border-box",
|
|
399
|
+
* "body": "font-family: system-ui, sans-serif; m:0 p:$3 bg:#434 fg:#d0dafa",
|
|
400
|
+
* "a": "text-decoration:none fg:#57f",
|
|
401
|
+
* "a:hover": "text-decoration:underline",
|
|
402
|
+
* "code": "font-family:monospace bg:#222 fg:#afc p:4px r:3px"
|
|
403
|
+
* });
|
|
404
|
+
*
|
|
405
|
+
* A('h2#Title without margins');
|
|
406
|
+
* A('a#This is a link');
|
|
407
|
+
* A('code#const x = 42;');
|
|
408
|
+
* ```
|
|
409
|
+
*
|
|
410
|
+
* @example Responsive Global Styles
|
|
411
|
+
* ```typescript
|
|
412
|
+
* A.insertGlobalCss({
|
|
413
|
+
* "html": "font-size:16px",
|
|
414
|
+
* "body": "line-height:1.6",
|
|
415
|
+
* "h1, h2, h3": "font-weight:600 mt:$4 mb:$2",
|
|
416
|
+
* "@media (max-width: 768px)": {
|
|
417
|
+
* "html": "font-size:14px",
|
|
418
|
+
* "body": "p:$2"
|
|
419
|
+
* },
|
|
420
|
+
* "@media (prefers-color-scheme: dark)": {
|
|
421
|
+
* "body": "bg:#1a1a1a fg:#e5e5e5",
|
|
422
|
+
* "code": "bg:#2a2a2a"
|
|
423
|
+
* }
|
|
424
|
+
* });
|
|
425
|
+
* ```
|
|
426
|
+
* At-rules such as `@media` and `@keyframes` should use nested objects. For keyframes,
|
|
427
|
+
* the step selectors (`0%`, `50%`, `100%`, etc.) become the nested keys and each value
|
|
428
|
+
* should be a concise CSS declaration string.
|
|
429
|
+
*
|
|
430
|
+
* @example Animation Keyframes
|
|
431
|
+
* ```typescript
|
|
432
|
+
* A.insertGlobalCss({
|
|
433
|
+
* "@keyframes connection-pulse": {
|
|
434
|
+
* "0%": "box-shadow: inset 0 0 0 0 rgba(255, 70, 70, 0.4);",
|
|
435
|
+
* "50%": "box-shadow: inset 0 0 0 6px rgba(255, 70, 70, 0.08);",
|
|
436
|
+
* "100%": "box-shadow: inset 0 0 0 0 rgba(255, 70, 70, 0.4);"
|
|
437
|
+
* }
|
|
438
|
+
* });
|
|
439
|
+
* ```
|
|
440
|
+
*/
|
|
441
|
+
insertGlobalCss(style: object): void;
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Creates a new string that has the opposite sort order compared to the input string.
|
|
445
|
+
*
|
|
446
|
+
* This is achieved by flipping the bits of each character code in the input string.
|
|
447
|
+
* The resulting string is intended for use as a sort key, particularly with the
|
|
448
|
+
* `makeKey` function in {@link Aberdeen.onEach}, to achieve a descending sort order.
|
|
449
|
+
*
|
|
450
|
+
* **Warning:** The output string will likely contain non-printable characters or
|
|
451
|
+
* appear as gibberish and should not be displayed to the user.
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* ```typescript
|
|
455
|
+
* const $users = A.proxy([
|
|
456
|
+
* { id: 1, name: 'Charlie', score: 95 },
|
|
457
|
+
* { id: 2, name: 'Alice', score: 100 },
|
|
458
|
+
* { id: 3, name: 'Bob', score: 90 },
|
|
459
|
+
* ]);
|
|
460
|
+
*
|
|
461
|
+
* A.onEach($users, ($user) => {
|
|
462
|
+
* A(`p#${$user.name}: ${$user.score}`);
|
|
463
|
+
* }, ($user) => A.invertString($user.name)); // Reverse alphabetic order
|
|
464
|
+
* ```
|
|
465
|
+
*
|
|
466
|
+
* @param input The string whose sort order needs to be inverted.
|
|
467
|
+
* @returns A new string that will sort in the reverse order of the input string.
|
|
468
|
+
* @see {@link Aberdeen.onEach} for usage with sorting.
|
|
469
|
+
*/
|
|
470
|
+
invertString(input: string): string;
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Reactively checks if an observable array, object, Map, or Set is empty.
|
|
474
|
+
*
|
|
475
|
+
* This function not only returns the current emptiness state but also establishes
|
|
476
|
+
* a reactive dependency. If the emptiness state of the `proxied` object or array
|
|
477
|
+
* changes later (e.g., an item is added to an empty array, or the last property
|
|
478
|
+
* is deleted from an object), the scope that called `isEmpty` will be automatically
|
|
479
|
+
* scheduled for re-evaluation.
|
|
480
|
+
*
|
|
481
|
+
* @param proxied The observable array, object, Map, or Set to check.
|
|
482
|
+
* @returns `true` if the array has length 0, the Map/Set has size 0, or the object has no own enumerable properties, `false` otherwise.
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```typescript
|
|
486
|
+
* const $items = A.proxy([]);
|
|
487
|
+
*
|
|
488
|
+
* // Reactively display a message if the items array is empty
|
|
489
|
+
* A('div', () => {
|
|
490
|
+
* if (A.isEmpty($items)) {
|
|
491
|
+
* A('p i#No items yet!');
|
|
492
|
+
* } else {
|
|
493
|
+
* A.onEach($items, item => A('p#'+item));
|
|
494
|
+
* }
|
|
495
|
+
* });
|
|
496
|
+
*
|
|
497
|
+
* // Adding an item will automatically remove the "No items yet!" message
|
|
498
|
+
* setInterval(() => {
|
|
499
|
+
* if (!$items.length || Math.random()>0.5) $items.push('Item');
|
|
500
|
+
* else $items.length = 0;
|
|
501
|
+
* }, 1000)
|
|
502
|
+
* ```
|
|
503
|
+
*/
|
|
504
|
+
isEmpty(proxied: TargetType): boolean;
|
|
505
|
+
|
|
506
|
+
/** When using a Map as `source`. */
|
|
507
|
+
map<K, IN, OUT>(
|
|
508
|
+
source: Map<K, IN>,
|
|
509
|
+
func: (value: IN, key: K) => undefined | OUT,
|
|
510
|
+
): Map<K, OUT>;
|
|
511
|
+
map<IN, OUT>(
|
|
512
|
+
source: Array<IN>,
|
|
513
|
+
func: (value: IN, index: number) => undefined | OUT,
|
|
514
|
+
): Array<OUT>;
|
|
515
|
+
map<IN, const IN_KEY extends string | number | symbol, OUT>(
|
|
516
|
+
source: Record<IN_KEY, IN>,
|
|
517
|
+
func: (value: IN, index: KeyToString<IN_KEY>) => undefined | OUT,
|
|
518
|
+
): Record<string | symbol, OUT>;
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Like {@link Aberdeen.copy}, but uses merge semantics. Properties in `dst` not present in `src` are kept.
|
|
522
|
+
* `null`/`undefined` in `src` delete properties in `dst`.
|
|
523
|
+
*
|
|
524
|
+
* @example Basic merge
|
|
525
|
+
* ```typescript
|
|
526
|
+
* const source = { b: { c: 99 }, d: undefined }; // d: undefined will delete
|
|
527
|
+
* const $dest = A.proxy({ a: 1, b: { x: 5 }, d: 4 });
|
|
528
|
+
* A.merge($dest, source);
|
|
529
|
+
* A.merge($dest, 'b', { y: 6 }); // merge into $dest.b
|
|
530
|
+
* A.merge($dest, 'c', { z: 7 }); // $dest.c doesn't exist yet, so it will just be assigned
|
|
531
|
+
* console.log($dest); // proxy({ a: 1, b: { c: 99, x: 5, y: 6 }, c: { z: 7 } })
|
|
532
|
+
* ```
|
|
533
|
+
*
|
|
534
|
+
*/
|
|
535
|
+
merge<T extends object>(dst: T, value: Partial<T>): boolean;
|
|
536
|
+
merge<T extends object>(dst: T, dstKey: keyof T, value: Partial<T[typeof dstKey]>): boolean;
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Attaches a reactive Aberdeen UI fragment to an existing DOM element. Without the use of
|
|
540
|
+
* this function, {@link Aberdeen | A} will assume `document.body` as its root.
|
|
541
|
+
*
|
|
542
|
+
* It creates a top-level reactive scope associated with the `parentElement`. The provided
|
|
543
|
+
* function `func` is executed immediately within this scope. Any proxied data read by `func`
|
|
544
|
+
* will cause it to re-execute when the data changes, updating the DOM elements created within it.
|
|
545
|
+
*
|
|
546
|
+
* Calls to {@link Aberdeen | A} inside `func` will append nodes to `parentElement`.
|
|
547
|
+
* You can nest {@link Aberdeen.derive} or other {@link Aberdeen | A} scopes within `func`.
|
|
548
|
+
* Use {@link Aberdeen.unmountAll} to clean up all mounted scopes and their DOM nodes.
|
|
549
|
+
*
|
|
550
|
+
* Mounting scopes happens reactively, meaning that if this function is called from within another
|
|
551
|
+
* ({@link Aberdeen.derive} or {@link Aberdeen | A} or {@link Aberdeen.mount}) scope that gets cleaned up, so will the mount.
|
|
552
|
+
*
|
|
553
|
+
* @param parentElement - The native DOM `Element` to which the UI fragment will be appended.
|
|
554
|
+
* @param func - The function that defines the UI fragment, typically containing calls to {@link Aberdeen | A}.
|
|
555
|
+
*
|
|
556
|
+
* @example Basic Mount
|
|
557
|
+
* ```javascript
|
|
558
|
+
* // Create a pre-existing DOM structure (without Aberdeen)
|
|
559
|
+
* document.body.innerHTML = `<h3>Static content <span id="title-extra"></span></h3><div class="box" id="app-root"></div>`;
|
|
560
|
+
*
|
|
561
|
+
* import A from 'aberdeen';
|
|
562
|
+
*
|
|
563
|
+
* const $runTime = A.proxy(0);
|
|
564
|
+
* setInterval(() => $runTime.value++, 1000);
|
|
565
|
+
*
|
|
566
|
+
* A.mount(document.getElementById('app-root'), () => {
|
|
567
|
+
* A('h4#Aberdeen App');
|
|
568
|
+
* A(`p#Run time: ${$runTime.value}s`);
|
|
569
|
+
* // Conditionally render some content somewhere else in the static page
|
|
570
|
+
* if ($runTime.value&1) {
|
|
571
|
+
* A.mount(document.getElementById('title-extra'), () =>
|
|
572
|
+
* A(`i#(${$runTime.value}s)`)
|
|
573
|
+
* );
|
|
574
|
+
* }
|
|
575
|
+
* });
|
|
576
|
+
* ```
|
|
577
|
+
*
|
|
578
|
+
* Note how the inner mount behaves reactively as well, automatically unmounting when it's parent observer scope re-runs.
|
|
579
|
+
*/
|
|
580
|
+
mount(parentElement: Element, func: () => void): void;
|
|
581
|
+
|
|
582
|
+
/** When using an array as `source`. */
|
|
583
|
+
multiMap<IN, OUT extends { [key: string | symbol]: any }>(
|
|
584
|
+
source: Array<IN>,
|
|
585
|
+
func: (value: IN, index: number) => OUT | undefined,
|
|
586
|
+
): OUT;
|
|
587
|
+
multiMap<
|
|
588
|
+
K extends string | number | symbol,
|
|
589
|
+
IN,
|
|
590
|
+
OUT extends { [key: string | symbol]: any },
|
|
591
|
+
>(source: Record<K, IN>, func: (value: IN, index: KeyToString<K>) => OUT | undefined): OUT;
|
|
592
|
+
multiMap<
|
|
593
|
+
K,
|
|
594
|
+
IN,
|
|
595
|
+
OUT extends { [key: string | symbol]: any },
|
|
596
|
+
>(source: Map<K, IN>, func: (value: IN, key: K) => OUT | undefined): OUT;
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* A symbol that controls how Aberdeen handles an object in copy operations and proxy wrapping.
|
|
600
|
+
*
|
|
601
|
+
* The **presence** of this symbol (regardless of its value) prevents deep-copying: the object is
|
|
602
|
+
* stored and passed by reference in {@link Aberdeen.clone} and {@link Aberdeen.copy}.
|
|
603
|
+
*
|
|
604
|
+
* The **value** of the symbol controls proxy wrapping when the object is read from reactive state:
|
|
605
|
+
* - **Truthy** (e.g. `true`): the object is fully opaque — it is not wrapped in a proxy, so its
|
|
606
|
+
* properties are not observable. Use this for objects that break when proxied (e.g. class instances
|
|
607
|
+
* with internal slots, Promises) or that must be invisible to Aberdeen's reactive system.
|
|
608
|
+
* - **Falsy** (e.g. `false`): the object is still wrapped in a proxy, so reads on its properties
|
|
609
|
+
* create reactive dependencies as normal — only deep-copying is suppressed.
|
|
610
|
+
*/
|
|
611
|
+
OPAQUE: any;
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Use {@link Aberdeen.OPAQUE} instead. This is an alias kept for backward compatibility.
|
|
615
|
+
*
|
|
616
|
+
* @deprecated
|
|
617
|
+
*/
|
|
618
|
+
NO_COPY: any;
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Reactively iterates over the items of an observable array, object, Map, or Set, optionally rendering content for each item.
|
|
622
|
+
*
|
|
623
|
+
* Automatically updates when items are added, removed, or modified.
|
|
624
|
+
*
|
|
625
|
+
* @param target The observable array, object, Map, or Set to iterate over. Values that are `undefined` are skipped.
|
|
626
|
+
* @param render A function called for each item. It receives the item's (observable) value and its index/key. For Sets, only the value is provided. Any DOM elements created within this function will be associated with the item, placed at the right spot in the DOM, and cleaned up when redrawing/removing the item.
|
|
627
|
+
* @param makeKey An optional function to generate a sort key for each item. This controls the order in which items are rendered in the DOM. If omitted, arrays use index order, Sets use the item value itself, and objects/Maps use their natural key order. The returned key can be a number, string, or an array of numbers/strings for composite sorting. Use {@link Aberdeen.invertString} on string keys for descending order. Returning `null` or `undefined` from `makeKey` will prevent the item from being rendered.
|
|
628
|
+
*
|
|
629
|
+
* @example Iterating an array
|
|
630
|
+
* ```typescript
|
|
631
|
+
* const $items = A.proxy(['apple', 'banana', 'cherry']);
|
|
632
|
+
*
|
|
633
|
+
* // Basic iteration
|
|
634
|
+
* A.onEach($items, (item, index) => A(`li#${item} (#${index})`));
|
|
635
|
+
*
|
|
636
|
+
* // Add a new item - the list updates automatically
|
|
637
|
+
* setTimeout(() => $items.push('durian'), 2000);
|
|
638
|
+
* // Same for updates and deletes
|
|
639
|
+
* setTimeout(() => $items[1] = 'berry', 4000);
|
|
640
|
+
* setTimeout(() => delete $items[2], 6000);
|
|
641
|
+
* ```
|
|
642
|
+
*
|
|
643
|
+
* @example Iterating an array with custom ordering
|
|
644
|
+
* ```typescript
|
|
645
|
+
* const $users = A.proxy([
|
|
646
|
+
* { id: 3, group: 1, name: 'Charlie' },
|
|
647
|
+
* { id: 1, group: 1, name: 'Alice' },
|
|
648
|
+
* { id: 2, group: 2, name: 'Bob' },
|
|
649
|
+
* ]);
|
|
650
|
+
*
|
|
651
|
+
* // Sort by name alphabetically
|
|
652
|
+
* A.onEach($users, ($user) => {
|
|
653
|
+
* A(`p#${$user.name} (id=${$user.id})`);
|
|
654
|
+
* }, ($user) => [$user.group, $user.name]); // Sort by group, and within each group sort by name
|
|
655
|
+
* ```
|
|
656
|
+
*
|
|
657
|
+
* @example Iterating an object
|
|
658
|
+
* ```javascript
|
|
659
|
+
* const $config = A.proxy({ theme: 'dark', fontSize: 14, showTips: true });
|
|
660
|
+
*
|
|
661
|
+
* // Display configuration options
|
|
662
|
+
* A('dl', () => {
|
|
663
|
+
* A.onEach($config, (value, key) => {
|
|
664
|
+
* if (key === 'showTips') return; // Don't render this one
|
|
665
|
+
* A('dt#'+key);
|
|
666
|
+
* A('dd#'+value);
|
|
667
|
+
* });
|
|
668
|
+
* });
|
|
669
|
+
*
|
|
670
|
+
* // Change a value - the display updates automatically
|
|
671
|
+
* setTimeout(() => $config.fontSize = 16, 2000);
|
|
672
|
+
* ```
|
|
673
|
+
*
|
|
674
|
+
* @example Iterating a Set
|
|
675
|
+
* ```javascript
|
|
676
|
+
* const $tags = A.proxy(new Set(['ui', 'fast', 'tiny']));
|
|
677
|
+
*
|
|
678
|
+
* A('ul', () => {
|
|
679
|
+
* A.onEach($tags, (tag) => { // Defaults to alphabetically ordering by tag
|
|
680
|
+
* A(`li#${tag}`);
|
|
681
|
+
* });
|
|
682
|
+
* });
|
|
683
|
+
*
|
|
684
|
+
* setTimeout(() => $tags.add('reactive'), 2000);
|
|
685
|
+
* ```
|
|
686
|
+
* @see {@link Aberdeen.invertString} To easily create keys for reverse sorting.
|
|
687
|
+
*/
|
|
688
|
+
onEach<K, T>(
|
|
689
|
+
target: Map<K, undefined | T>,
|
|
690
|
+
render: (value: T, key: K) => void,
|
|
691
|
+
makeKey?: (value: T, key: K) => SortKeyType,
|
|
692
|
+
): void;
|
|
693
|
+
onEach<T>(
|
|
694
|
+
target: Set<T>,
|
|
695
|
+
render: (value: T) => void,
|
|
696
|
+
makeKey?: (value: T) => SortKeyType,
|
|
697
|
+
): void;
|
|
698
|
+
onEach<T>(
|
|
699
|
+
target: ReadonlyArray<undefined | T>,
|
|
700
|
+
render: (value: T, index: number) => void,
|
|
701
|
+
makeKey?: (value: T, index: number) => SortKeyType,
|
|
702
|
+
): void;
|
|
703
|
+
onEach<K extends string | number | symbol, T>(
|
|
704
|
+
target: Record<K, undefined | T>,
|
|
705
|
+
render: (value: T, index: KeyToString<K>) => void,
|
|
706
|
+
makeKey?: (value: T, index: KeyToString<K>) => SortKeyType,
|
|
707
|
+
): void;
|
|
708
|
+
|
|
709
|
+
/** When using an object as `array`. */
|
|
710
|
+
partition<OUT_K extends string | number | symbol, IN_V>(
|
|
711
|
+
source: IN_V[],
|
|
712
|
+
func: (value: IN_V, key: number) => undefined | OUT_K | OUT_K[],
|
|
713
|
+
): Record<OUT_K, Record<number, IN_V>>;
|
|
714
|
+
partition<
|
|
715
|
+
IN_K extends string | number | symbol,
|
|
716
|
+
OUT_K extends string | number | symbol,
|
|
717
|
+
IN_V,
|
|
718
|
+
>(
|
|
719
|
+
source: Record<IN_K, IN_V>,
|
|
720
|
+
func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
|
|
721
|
+
): Record<OUT_K, Record<IN_K, IN_V>>;
|
|
722
|
+
partition<
|
|
723
|
+
IN_K extends string | number | symbol,
|
|
724
|
+
OUT_K extends string | number | symbol,
|
|
725
|
+
IN_V,
|
|
726
|
+
>(
|
|
727
|
+
source: Map<IN_K, IN_V>,
|
|
728
|
+
func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
|
|
729
|
+
): Record<OUT_K, Record<IN_K, IN_V>>;
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Executes a function or retrieves a value *without* creating subscriptions in the current reactive scope, and returns its result.
|
|
733
|
+
*
|
|
734
|
+
* This is useful when you need to access reactive data inside a reactive scope (like {@link Aberdeen | A})
|
|
735
|
+
* but do not want changes to that specific data to trigger a re-execute of the scope.
|
|
736
|
+
*
|
|
737
|
+
* Note: You may also use {@link Aberdeen.unproxy} to get to the raw underlying data structure, which can be used to similar effect.
|
|
738
|
+
*
|
|
739
|
+
* @param target - Either a function to execute, or an object (which may also be an Array or a Map) to index.
|
|
740
|
+
* @param key - Optional key/index to use when `target` is an object.
|
|
741
|
+
* @returns The result of the function call, or the value at `target[key]` when `target` is an object or `target.get(key)` when it's a Map.
|
|
742
|
+
*
|
|
743
|
+
* @example Peeking within observer
|
|
744
|
+
* ```typescript
|
|
745
|
+
* const $data = A.proxy({ a: 1, b: 2 });
|
|
746
|
+
* A(() => {
|
|
747
|
+
* // re-executes only when $data.a changes, because $data.b is peeked.
|
|
748
|
+
* const b = A.peek(() => $data.b);
|
|
749
|
+
* console.log(`A is ${$data.a}, B was ${b} when A changed.`);
|
|
750
|
+
* });
|
|
751
|
+
* $data.b = 3; // Does not trigger console.log
|
|
752
|
+
* $data.a = 2; // Triggers console.log (logs "A is 2, B was 3 when A changed.")
|
|
753
|
+
* ```
|
|
754
|
+
*
|
|
755
|
+
*/
|
|
756
|
+
peek<T extends object, K extends keyof T>(target: T, key: K): T[K];
|
|
757
|
+
peek<K,V>(target: Map<K,V>, key: K): V | undefined;
|
|
758
|
+
peek<T>(target: T[], key: number): T | undefined;
|
|
759
|
+
peek<T>(target: () => T): T;
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Creates a reactive proxy around the given data.
|
|
763
|
+
*
|
|
764
|
+
* Reading properties from the returned proxy within a reactive scope (like one created by
|
|
765
|
+
* {@link Aberdeen | A} or {@link Aberdeen.derive}) establishes a subscription. Modifying properties *through*
|
|
766
|
+
* the proxy will notify subscribed scopes, causing them to re-execute.
|
|
767
|
+
*
|
|
768
|
+
* - Plain objects, arrays, Maps, and Sets are wrapped in a standard JavaScript `Proxy` that intercepts
|
|
769
|
+
* property access and mutations, but otherwise works like the underlying data.
|
|
770
|
+
* - Primitives (string, number, boolean, null, undefined) are wrapped in an object
|
|
771
|
+
* `{ value: T }` which is then proxied. Access the primitive via the `.value` property.
|
|
772
|
+
* - Promises are represented by proxied objects `{ busy: boolean, value?: T, error?: any }`.
|
|
773
|
+
* Initially, `busy` is `true`. When the promise resolves, `value` is set and `busy`
|
|
774
|
+
* is set to `false`. If the promise is rejected, `error` is set and `busy` is also
|
|
775
|
+
* set to `false`.
|
|
776
|
+
*
|
|
777
|
+
* Use {@link Aberdeen.unproxy} to get the original underlying data back.
|
|
778
|
+
* By convention in the examples below, local variables that hold proxied values are prefixed with `$`.
|
|
779
|
+
*
|
|
780
|
+
* @param target - The object, array, Map, Set, or primitive value to make reactive.
|
|
781
|
+
* @returns A reactive proxy wrapping the target data.
|
|
782
|
+
* @template T - The type of the data being proxied.
|
|
783
|
+
*
|
|
784
|
+
* @example Object
|
|
785
|
+
* ```javascript
|
|
786
|
+
* const $state = A.proxy({ count: 0, message: 'Hello' });
|
|
787
|
+
* A(() => console.log($state.message)); // Subscribes to message
|
|
788
|
+
* setTimeout(() => $state.message = 'World', 1000); // Triggers the observing function
|
|
789
|
+
* setTimeout(() => $state.count++, 2000); // Triggers nothing
|
|
790
|
+
* ```
|
|
791
|
+
*
|
|
792
|
+
* @example Array
|
|
793
|
+
* ```javascript
|
|
794
|
+
* const $items = A.proxy(['a', 'b']);
|
|
795
|
+
* A(() => console.log($items.length)); // Subscribes to length
|
|
796
|
+
* setTimeout(() => $items.push('c'), 2000); // Triggers the observing function
|
|
797
|
+
* ```
|
|
798
|
+
*
|
|
799
|
+
* @example Primitive
|
|
800
|
+
* ```javascript
|
|
801
|
+
* const $name = A.proxy('Aberdeen');
|
|
802
|
+
* A(() => console.log($name.value)); // Subscribes to value
|
|
803
|
+
* setTimeout(() => $name.value = 'UI', 2000); // Triggers the observing function
|
|
804
|
+
* ```
|
|
805
|
+
*
|
|
806
|
+
* @example Set
|
|
807
|
+
* ```javascript
|
|
808
|
+
* const $tags = A.proxy(new Set(['ui', 'tiny']));
|
|
809
|
+
* A(() => console.log($tags.has('ui'), $tags.size));
|
|
810
|
+
* setTimeout(() => $tags.add('fast'), 1000);
|
|
811
|
+
* ```
|
|
812
|
+
*
|
|
813
|
+
* @example Class instance
|
|
814
|
+
* ```typescript
|
|
815
|
+
* class Widget {
|
|
816
|
+
* constructor(public name: string, public width: number, public height: number) {}
|
|
817
|
+
* grow() { this.width *= 2; }
|
|
818
|
+
* toString() { return `${this.name}Widget (${this.width}x${this.height})`; }
|
|
819
|
+
* }
|
|
820
|
+
* let $graph: Widget = A.proxy(new Widget('Graph', 200, 100));
|
|
821
|
+
* A(() => console.log(''+$graph));
|
|
822
|
+
* setTimeout(() => $graph.grow(), 2000);
|
|
823
|
+
* setTimeout(() => $graph.grow(), 4000);
|
|
824
|
+
* ```
|
|
825
|
+
*/
|
|
826
|
+
proxy<T extends any>(target: Promise<T>): PromiseProxy<T>;
|
|
827
|
+
proxy<T extends any>(target: Array<T>): Array<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T >;
|
|
828
|
+
proxy<T extends object>(target: T): T;
|
|
829
|
+
proxy<T extends any>(target: T): ValueRef<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T>;
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Creates a reactive reference (`{ value: T }`-like object) to a specific value
|
|
833
|
+
* within a proxied object or array.
|
|
834
|
+
*
|
|
835
|
+
* This is primarily used for the `bind` property in {@link Aberdeen | A} to create two-way data bindings
|
|
836
|
+
* with form elements, and for passing a reactive property to any of the {@link Aberdeen | A} key-value pairs.
|
|
837
|
+
*
|
|
838
|
+
* Reading `ref.value` accesses the property from the underlying proxy (and subscribes the current scope).
|
|
839
|
+
* Assigning to `ref.value` updates the property in the underlying proxy (triggering reactive updates).
|
|
840
|
+
*
|
|
841
|
+
* @param target - The reactive proxy (created by {@link Aberdeen.proxy}) containing the target property.
|
|
842
|
+
* @param index - The key (for objects) or index (for arrays) of the property to reference.
|
|
843
|
+
* @returns A reference object with a `value` property linked to the specified proxy property.
|
|
844
|
+
*
|
|
845
|
+
* @example
|
|
846
|
+
* ```javascript
|
|
847
|
+
* const $formData = A.proxy({ color: 'orange', velocity: 42 });
|
|
848
|
+
*
|
|
849
|
+
* // Usage with `bind`
|
|
850
|
+
* A('input type=text bind=', A.ref($formData, 'color'));
|
|
851
|
+
*
|
|
852
|
+
* // Usage as a dynamic property, causes a TextNode with just the name to be created and live-updated
|
|
853
|
+
* A('p text="Selected color: " text=', A.ref($formData, 'color'), 'color:', A.ref($formData, 'color'));
|
|
854
|
+
*
|
|
855
|
+
* // Changes are actually stored in $formData - this causes logs like `{color: "Blue", velocity 42}`
|
|
856
|
+
* A(() => console.log($formData))
|
|
857
|
+
* ```
|
|
858
|
+
*/
|
|
859
|
+
ref<T extends TargetType, K extends keyof T>(
|
|
860
|
+
target: T,
|
|
861
|
+
index: K,
|
|
862
|
+
): ValueRef<T[K]>;
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Forces the immediate and synchronous execution of all pending reactive updates.
|
|
866
|
+
*
|
|
867
|
+
* Normally, changes to observed data sources (like proxied objects or arrays)
|
|
868
|
+
* are processed asynchronously in a batch after a brief timeout (0ms). This function
|
|
869
|
+
* allows you to bypass the timeout and process the update queue immediately.
|
|
870
|
+
*
|
|
871
|
+
* This can be useful in specific scenarios where you need the DOM to be updated
|
|
872
|
+
* synchronously.
|
|
873
|
+
*
|
|
874
|
+
* This function is re-entrant, meaning it is safe to call `runQueue` from within
|
|
875
|
+
* a function that is itself being executed as part of an update cycle triggered
|
|
876
|
+
* by a previous (or the same) `runQueue` call.
|
|
877
|
+
*
|
|
878
|
+
* @example
|
|
879
|
+
* ```typescript
|
|
880
|
+
* const $data = A.proxy("before");
|
|
881
|
+
*
|
|
882
|
+
* A('#', $data);
|
|
883
|
+
* console.log(1, document.body.innerHTML); // before
|
|
884
|
+
*
|
|
885
|
+
* // Make an update that should cause the DOM to change.
|
|
886
|
+
* $data.value = "after";
|
|
887
|
+
*
|
|
888
|
+
* // Normally, the DOM update would happen after a timeout.
|
|
889
|
+
* // But this causes an immediate update:
|
|
890
|
+
* A.runQueue();
|
|
891
|
+
*
|
|
892
|
+
* console.log(2, document.body.innerHTML); // after
|
|
893
|
+
* ```
|
|
894
|
+
*/
|
|
895
|
+
runQueue(): void;
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Sets a custom error handler function for errors that occur asynchronously
|
|
899
|
+
* within reactive scopes (e.g., during updates triggered by proxy changes in
|
|
900
|
+
* {@link Aberdeen.derive} or {@link Aberdeen | A} render functions).
|
|
901
|
+
*
|
|
902
|
+
* The default handler logs the error to `console.error` and adds a simple
|
|
903
|
+
* 'Error' message div to the DOM at the location where the error occurred (if possible).
|
|
904
|
+
*
|
|
905
|
+
* Your handler can provide custom logging, UI feedback, or suppress the default
|
|
906
|
+
* error message.
|
|
907
|
+
*
|
|
908
|
+
* @param handler - A function that accepts the `Error` object.
|
|
909
|
+
* - Return `false` to prevent adding an error message to the DOM.
|
|
910
|
+
* - Return `true` or `undefined` (or throw) to allow the error messages to be added to the DOM.
|
|
911
|
+
*
|
|
912
|
+
* @example Custom Logging and Suppressing Default Message
|
|
913
|
+
* ```typescript
|
|
914
|
+
* A.setErrorHandler(error => {
|
|
915
|
+
* console.warn('Aberdeen render error:', error.message);
|
|
916
|
+
* // Log to error reporting service
|
|
917
|
+
* // myErrorReporter.log(error);
|
|
918
|
+
*
|
|
919
|
+
* try {
|
|
920
|
+
* // Attempt to show a custom message in the UI
|
|
921
|
+
* A('div#Oops, something went wrong!', errorClass);
|
|
922
|
+
* } catch (e) {
|
|
923
|
+
* // Ignore errors during error handling itself
|
|
924
|
+
* }
|
|
925
|
+
*
|
|
926
|
+
* return false; // Suppress default console log and DOM error message
|
|
927
|
+
* });
|
|
928
|
+
*
|
|
929
|
+
* // Styling for our custom error message
|
|
930
|
+
* const errorClass = A.insertCss('background-color:#e31f00 display:inline-block color:white r:3px padding: 2px 4px;');
|
|
931
|
+
*
|
|
932
|
+
* // Cause an error within a render scope.
|
|
933
|
+
* A('div.box', () => {
|
|
934
|
+
* // Will cause our error handler to insert an error message within the box
|
|
935
|
+
* noSuchFunction();
|
|
936
|
+
* })
|
|
937
|
+
* ```
|
|
938
|
+
*/
|
|
939
|
+
setErrorHandler(
|
|
940
|
+
handler?: (error: Error) => boolean | undefined,
|
|
941
|
+
): void;
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Initializes `cssVars[0]` through `cssVars[12]` with an exponential spacing scale.
|
|
945
|
+
*
|
|
946
|
+
* The scale is calculated as `2^(n-3) * base`, providing values from `0.25 * base` to `512 * base`.
|
|
947
|
+
*
|
|
948
|
+
* @param base - The base size for the spacing scale that will apply to `cssVars[3]`. Every step up the scale will double this, while every step down will halve it. Defaults to 1.
|
|
949
|
+
* @param unit - The CSS unit to use, like 'rem', 'em', or 'px'. Defaults to 'rem'.
|
|
950
|
+
*
|
|
951
|
+
* @example
|
|
952
|
+
* ```javascript
|
|
953
|
+
* import A from 'aberdeen';
|
|
954
|
+
* // Use default scale (0.25rem to 512rem)
|
|
955
|
+
* A.setSpacingCssVars();
|
|
956
|
+
*
|
|
957
|
+
* // Use custom base size
|
|
958
|
+
* A.setSpacingCssVars(16, 'px'); // 4px to 8192px
|
|
959
|
+
*
|
|
960
|
+
* // Use em units
|
|
961
|
+
* A.setSpacingCssVars(1, 'em'); // 0.25em to 512em
|
|
962
|
+
*
|
|
963
|
+
* // Show the last generated spacing values
|
|
964
|
+
* A.onEach(A.cssVars, (value, key) => {
|
|
965
|
+
* A(`div #${key} → ${value}`)
|
|
966
|
+
* }, (value, key) => parseInt(key)); // Numeric sort
|
|
967
|
+
* ```
|
|
968
|
+
*/
|
|
969
|
+
setSpacingCssVars(base?: number, unit?: string): void;
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Removes all Aberdeen-managed DOM nodes and stops all active reactive scopes
|
|
973
|
+
* (created by {@link Aberdeen.mount}, {@link Aberdeen.derive}, {@link Aberdeen | A} with functions, etc.).
|
|
974
|
+
*
|
|
975
|
+
* This effectively cleans up the entire Aberdeen application state. Aside from in
|
|
976
|
+
* automated tests, there should probably be little reason to call this function.
|
|
977
|
+
*/
|
|
978
|
+
unmountAll(): void;
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Returns the original, underlying data target from a reactive proxy created by {@link Aberdeen.proxy}.
|
|
982
|
+
* If the input `target` is not a proxy, it is returned directly.
|
|
983
|
+
*
|
|
984
|
+
* This is useful when you want to avoid triggering subscriptions during read operations or
|
|
985
|
+
* re-executes during write operations. Using {@link Aberdeen.peek} is an alternative way to achieve this.
|
|
986
|
+
*
|
|
987
|
+
* @param target - A proxied object, array, or any other value.
|
|
988
|
+
* @returns The underlying (unproxied) data, or the input value if it wasn't a proxy.
|
|
989
|
+
* @template T - The type of the target.
|
|
990
|
+
*
|
|
991
|
+
* @example
|
|
992
|
+
* ```typescript
|
|
993
|
+
* const $user = A.proxy({ name: 'Frank' });
|
|
994
|
+
* const rawUser = A.unproxy($user);
|
|
995
|
+
*
|
|
996
|
+
* // Log reactively
|
|
997
|
+
* A(() => console.log('proxied', $user.name));
|
|
998
|
+
* // The following will only ever log once, as we're not subscribing to any observable
|
|
999
|
+
* A(() => console.log('unproxied', rawUser.name));
|
|
1000
|
+
*
|
|
1001
|
+
* // This cause the first log to run again:
|
|
1002
|
+
* setTimeout(() => $user.name += '!', 1000);
|
|
1003
|
+
*
|
|
1004
|
+
* // This doesn't cause any new logs:
|
|
1005
|
+
* setTimeout(() => rawUser.name += '?', 2000);
|
|
1006
|
+
*
|
|
1007
|
+
* // Both $user and rawUser end up as `{name: 'Frank!?'}`
|
|
1008
|
+
* setTimeout(() => {
|
|
1009
|
+
* console.log('final proxied', $user)
|
|
1010
|
+
* console.log('final unproxied', rawUser)
|
|
1011
|
+
* }, 3000);
|
|
1012
|
+
* ```
|
|
1013
|
+
*/
|
|
1014
|
+
unproxy<T>(target: T): T;
|
|
1015
|
+
|
|
1016
|
+
/** @internal The window this instance renders against. */
|
|
1017
|
+
window: any;
|
|
1018
|
+
/** @internal */
|
|
1019
|
+
EMPTY: any;
|
|
1020
|
+
|
|
1021
|
+
/** @internal */
|
|
1022
|
+
MAP_SIZE_SYMBOL: any;
|
|
1023
|
+
|
|
1024
|
+
/** @internal */
|
|
1025
|
+
leakScope<T>(func: () => T): T;
|
|
1026
|
+
|
|
1027
|
+
/** @internal */
|
|
1028
|
+
withEmitHandler(
|
|
1029
|
+
handler: (
|
|
1030
|
+
target: TargetType,
|
|
1031
|
+
index: any,
|
|
1032
|
+
newData: any,
|
|
1033
|
+
oldData: any,
|
|
1034
|
+
) => void,
|
|
1035
|
+
func: () => void,
|
|
1036
|
+
): void;
|
|
1037
|
+
|
|
1038
|
+
/** @internal */
|
|
1039
|
+
defaultEmitHandler(
|
|
1040
|
+
target: TargetType,
|
|
1041
|
+
index: string | symbol | number,
|
|
1042
|
+
newData: any,
|
|
1043
|
+
oldData: any,
|
|
1044
|
+
): void;
|
|
1045
|
+
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/** The shape returned by {@link Aberdeen.ref}: an object whose single `value` property is reactive. */
|
|
1049
|
+
export interface ValueRef<T> {
|
|
1050
|
+
value: T;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/** When `proxy` is called with a Promise, the returned object has this shape. */
|
|
1054
|
+
export interface PromiseProxy<T> {
|
|
1055
|
+
/** True while the promise is pending, false once it has resolved or rejected. */
|
|
1056
|
+
busy: boolean;
|
|
1057
|
+
/** If the promise has resolved, the resolved value. */
|
|
1058
|
+
value?: T;
|
|
1059
|
+
/** If the promise has rejected, the rejection error. */
|
|
1060
|
+
error?: any;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Create an isolated Aberdeen instance bound to the given window. Returns the callable `A`
|
|
1065
|
+
* object (with all Aberdeen functions attached as properties).
|
|
1066
|
+
*
|
|
1067
|
+
* In the browser you normally import the pre-initialised default instance
|
|
1068
|
+
* (`import A from 'aberdeen'`). Call `init` yourself to render against a non-browser DOM
|
|
1069
|
+
* (see `aberdeen/server`) or to create multiple isolated instances. All reactive state lives
|
|
1070
|
+
* in the returned instance, so separate instances never interfere.
|
|
1071
|
+
*
|
|
1072
|
+
* @param window The window to render against. May be omitted for the browser default when
|
|
1073
|
+
* no `window` global exists yet; rendering then throws until a real window is available.
|
|
1074
|
+
* @param options See {@link InitOptions}.
|
|
1075
|
+
*/
|
|
1076
|
+
export function createA(window?: WindowLike, options: InitOptions = {}): Aberdeen {
|
|
1077
|
+
|
|
1078
|
+
// Everything the DOM-touching code needs comes from the injected window, so the code below
|
|
1079
|
+
// can use `document`, `Node`, `Element`, `setTimeout` and `queueMicrotask` exactly as it would
|
|
1080
|
+
// against the browser globals. Timers fall back to the host's when the window doesn't provide
|
|
1081
|
+
// them (e.g. the lean server window relies on Node's timers).
|
|
1082
|
+
const document = (window ? window.document : undefined) as Document;
|
|
1083
|
+
const Node = (window ? window.Node : undefined) as typeof globalThis.Node;
|
|
1084
|
+
const Element = (window ? window.Element : undefined) as typeof globalThis.Element;
|
|
1085
|
+
const setTimeout = (window?.setTimeout ? window.setTimeout.bind(window) : globalThis.setTimeout) as typeof globalThis.setTimeout;
|
|
1086
|
+
const queueMicrotask = (window?.queueMicrotask ? window.queueMicrotask.bind(window) : globalThis.queueMicrotask) as typeof globalThis.queueMicrotask;
|
|
1087
|
+
|
|
17
1088
|
let sortedQueue: ReverseSortedSet<QueueRunner, "prio"> | undefined; // When set, a runQueue is scheduled or currently running.
|
|
18
1089
|
let runQueueDepth = 0; // Incremented when a queue event causes another queue event to be added. Reset when queue is empty. Throw when >= 42 to break (infinite) recursion.
|
|
19
1090
|
let topRedrawScope: Scope | undefined; // The scope that triggered the current redraw. Elements drawn at this scope level may trigger 'create' animations.
|
|
@@ -57,7 +1128,6 @@ function undoSideEffects(el: any, se: any[]) {
|
|
|
57
1128
|
}
|
|
58
1129
|
|
|
59
1130
|
/** @internal */
|
|
60
|
-
export type TargetType = any[] | { [key: string | symbol]: any } | Map<any, any> | Set<any>;
|
|
61
1131
|
|
|
62
1132
|
|
|
63
1133
|
function queue(runner: QueueRunner) {
|
|
@@ -73,38 +1143,7 @@ function queue(runner: QueueRunner) {
|
|
|
73
1143
|
sortedQueue.add(runner);
|
|
74
1144
|
}
|
|
75
1145
|
|
|
76
|
-
|
|
77
|
-
* Forces the immediate and synchronous execution of all pending reactive updates.
|
|
78
|
-
*
|
|
79
|
-
* Normally, changes to observed data sources (like proxied objects or arrays)
|
|
80
|
-
* are processed asynchronously in a batch after a brief timeout (0ms). This function
|
|
81
|
-
* allows you to bypass the timeout and process the update queue immediately.
|
|
82
|
-
*
|
|
83
|
-
* This can be useful in specific scenarios where you need the DOM to be updated
|
|
84
|
-
* synchronously.
|
|
85
|
-
*
|
|
86
|
-
* This function is re-entrant, meaning it is safe to call `runQueue` from within
|
|
87
|
-
* a function that is itself being executed as part of an update cycle triggered
|
|
88
|
-
* by a previous (or the same) `runQueue` call.
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* ```typescript
|
|
92
|
-
* const $data = A.proxy("before");
|
|
93
|
-
*
|
|
94
|
-
* A('#', $data);
|
|
95
|
-
* console.log(1, document.body.innerHTML); // before
|
|
96
|
-
*
|
|
97
|
-
* // Make an update that should cause the DOM to change.
|
|
98
|
-
* $data.value = "after";
|
|
99
|
-
*
|
|
100
|
-
* // Normally, the DOM update would happen after a timeout.
|
|
101
|
-
* // But this causes an immediate update:
|
|
102
|
-
* A.runQueue();
|
|
103
|
-
*
|
|
104
|
-
* console.log(2, document.body.innerHTML); // after
|
|
105
|
-
* ```
|
|
106
|
-
*/
|
|
107
|
-
export function runQueue(): void {
|
|
1146
|
+
function runQueue(): void {
|
|
108
1147
|
let time = Date.now();
|
|
109
1148
|
while (sortedQueue) {
|
|
110
1149
|
const runner = sortedQueue.fetchLast();
|
|
@@ -119,13 +1158,12 @@ export function runQueue(): void {
|
|
|
119
1158
|
}
|
|
120
1159
|
|
|
121
1160
|
/**
|
|
122
|
-
* A sort key, as used by {@link onEach}, is a value that determines the order of items. It can
|
|
1161
|
+
* A sort key, as used by {@link Aberdeen.onEach}, is a value that determines the order of items. It can
|
|
123
1162
|
* be a number, string, or an array of numbers/strings. The sort key is used to sort items
|
|
124
1163
|
* based on their values. The sort key can also be `undefined`, which indicates that the item
|
|
125
1164
|
* should be ignored.
|
|
126
1165
|
* @internal
|
|
127
1166
|
*/
|
|
128
|
-
export type SortKeyType = number | string | Array<number | string> | undefined | void;
|
|
129
1167
|
|
|
130
1168
|
/**
|
|
131
1169
|
* Given an array of integer numbers or strings, this function returns a string that sorts
|
|
@@ -162,34 +1200,7 @@ function arrayToStr(parts: (number | string)[]): string {
|
|
|
162
1200
|
return result;
|
|
163
1201
|
}
|
|
164
1202
|
|
|
165
|
-
|
|
166
|
-
* Creates a new string that has the opposite sort order compared to the input string.
|
|
167
|
-
*
|
|
168
|
-
* This is achieved by flipping the bits of each character code in the input string.
|
|
169
|
-
* The resulting string is intended for use as a sort key, particularly with the
|
|
170
|
-
* `makeKey` function in {@link onEach}, to achieve a descending sort order.
|
|
171
|
-
*
|
|
172
|
-
* **Warning:** The output string will likely contain non-printable characters or
|
|
173
|
-
* appear as gibberish and should not be displayed to the user.
|
|
174
|
-
*
|
|
175
|
-
* @example
|
|
176
|
-
* ```typescript
|
|
177
|
-
* const $users = A.proxy([
|
|
178
|
-
* { id: 1, name: 'Charlie', score: 95 },
|
|
179
|
-
* { id: 2, name: 'Alice', score: 100 },
|
|
180
|
-
* { id: 3, name: 'Bob', score: 90 },
|
|
181
|
-
* ]);
|
|
182
|
-
*
|
|
183
|
-
* A.onEach($users, ($user) => {
|
|
184
|
-
* A(`p#${$user.name}: ${$user.score}`);
|
|
185
|
-
* }, ($user) => A.invertString($user.name)); // Reverse alphabetic order
|
|
186
|
-
* ```
|
|
187
|
-
*
|
|
188
|
-
* @param input The string whose sort order needs to be inverted.
|
|
189
|
-
* @returns A new string that will sort in the reverse order of the input string.
|
|
190
|
-
* @see {@link onEach} for usage with sorting.
|
|
191
|
-
*/
|
|
192
|
-
export function invertString(input: string): string {
|
|
1203
|
+
function invertString(input: string): string {
|
|
193
1204
|
let result = "";
|
|
194
1205
|
for (let i = 0; i < input.length; i++) {
|
|
195
1206
|
result += String.fromCodePoint(65535 - input.charCodeAt(i));
|
|
@@ -433,7 +1444,7 @@ class RegularScope extends ChainedScope {
|
|
|
433
1444
|
}
|
|
434
1445
|
|
|
435
1446
|
class RootScope extends ContentScope {
|
|
436
|
-
el = document.body;
|
|
1447
|
+
el = document ? document.body : (undefined as any);
|
|
437
1448
|
svg = false;
|
|
438
1449
|
getPrecedingNode(): Node | undefined {
|
|
439
1450
|
return undefined;
|
|
@@ -516,7 +1527,7 @@ function removeNodes(
|
|
|
516
1527
|
function findLastNodeInPrevSiblings(
|
|
517
1528
|
sibling: Node | Scope | undefined,
|
|
518
1529
|
): Node | undefined {
|
|
519
|
-
if (!sibling || sibling instanceof
|
|
1530
|
+
if (!sibling || !(sibling instanceof Scope)) return sibling;
|
|
520
1531
|
return sibling.getLastNode() || sibling.getPrecedingNode();
|
|
521
1532
|
}
|
|
522
1533
|
|
|
@@ -732,7 +1743,7 @@ class OnEachItemScope extends ContentScope {
|
|
|
732
1743
|
let child = this.lastChild;
|
|
733
1744
|
|
|
734
1745
|
while (child && child !== this) {
|
|
735
|
-
if (child instanceof
|
|
1746
|
+
if (!(child instanceof Scope)) return child;
|
|
736
1747
|
const node = child.getLastNode();
|
|
737
1748
|
if (node) return node;
|
|
738
1749
|
child = child.getPrecedingNode();
|
|
@@ -865,14 +1876,7 @@ function addNode(el: Element, node: Node) {
|
|
|
865
1876
|
const ROOT_SCOPE = new RootScope();
|
|
866
1877
|
let currentScope: ContentScope = ROOT_SCOPE;
|
|
867
1878
|
|
|
868
|
-
|
|
869
|
-
* Execute a function in a never-cleaned root scope. Even {@link unmountAll} will not
|
|
870
|
-
* clean up observers/nodes created by the function.
|
|
871
|
-
* @param func The function to execute.
|
|
872
|
-
* @returns The return value of the function.
|
|
873
|
-
* @internal
|
|
874
|
-
*/
|
|
875
|
-
export function leakScope<T>(func: () => T): T {
|
|
1879
|
+
function leakScope<T>(func: () => T): T {
|
|
876
1880
|
const savedScope = currentScope;
|
|
877
1881
|
currentScope = new RootScope();
|
|
878
1882
|
try {
|
|
@@ -893,12 +1897,7 @@ const ANY_SYMBOL = Symbol("any");
|
|
|
893
1897
|
*/
|
|
894
1898
|
const TARGET_SYMBOL = Symbol("target");
|
|
895
1899
|
|
|
896
|
-
|
|
897
|
-
* Symbol used internally to track Map and Set size without clashing with actual Map keys named "size".
|
|
898
|
-
*
|
|
899
|
-
* @internal
|
|
900
|
-
*/
|
|
901
|
-
export const MAP_SIZE_SYMBOL = Symbol("mapSize");
|
|
1900
|
+
const MAP_SIZE_SYMBOL = Symbol("mapSize");
|
|
902
1901
|
|
|
903
1902
|
const subscribers = new WeakMap<
|
|
904
1903
|
TargetType,
|
|
@@ -949,24 +1948,23 @@ function subscribe(
|
|
|
949
1948
|
* This type changes (number | something) types to (string | something) types, maintaining typing precision as much as possible.
|
|
950
1949
|
* @internal
|
|
951
1950
|
*/
|
|
952
|
-
type KeyToString<K> = K extends number ? string : K extends string | symbol ? K : K extends number | infer U ? string | U : K;
|
|
953
1951
|
|
|
954
|
-
|
|
1952
|
+
function onEach<K, T>(
|
|
955
1953
|
target: Map<K, undefined | T>,
|
|
956
1954
|
render: (value: T, key: K) => void,
|
|
957
1955
|
makeKey?: (value: T, key: K) => SortKeyType,
|
|
958
1956
|
): void;
|
|
959
|
-
|
|
1957
|
+
function onEach<T>(
|
|
960
1958
|
target: Set<T>,
|
|
961
1959
|
render: (value: T) => void,
|
|
962
1960
|
makeKey?: (value: T) => SortKeyType,
|
|
963
1961
|
): void;
|
|
964
|
-
|
|
1962
|
+
function onEach<T>(
|
|
965
1963
|
target: ReadonlyArray<undefined | T>,
|
|
966
1964
|
render: (value: T, index: number) => void,
|
|
967
1965
|
makeKey?: (value: T, index: number) => SortKeyType,
|
|
968
1966
|
): void;
|
|
969
|
-
|
|
1967
|
+
function onEach<K extends string | number | symbol, T>(
|
|
970
1968
|
target: Record<K, undefined | T>,
|
|
971
1969
|
render: (value: T, index: KeyToString<K>) => void,
|
|
972
1970
|
makeKey?: (value: T, index: KeyToString<K>) => SortKeyType,
|
|
@@ -979,7 +1977,7 @@ export function onEach<K extends string | number | symbol, T>(
|
|
|
979
1977
|
*
|
|
980
1978
|
* @param target The observable array, object, Map, or Set to iterate over. Values that are `undefined` are skipped.
|
|
981
1979
|
* @param render A function called for each item. It receives the item's (observable) value and its index/key. For Sets, only the value is provided. Any DOM elements created within this function will be associated with the item, placed at the right spot in the DOM, and cleaned up when redrawing/removing the item.
|
|
982
|
-
* @param makeKey An optional function to generate a sort key for each item. This controls the order in which items are rendered in the DOM. If omitted, arrays use index order, Sets use the item value itself, and objects/Maps use their natural key order. The returned key can be a number, string, or an array of numbers/strings for composite sorting. Use {@link invertString} on string keys for descending order. Returning `null` or `undefined` from `makeKey` will prevent the item from being rendered.
|
|
1980
|
+
* @param makeKey An optional function to generate a sort key for each item. This controls the order in which items are rendered in the DOM. If omitted, arrays use index order, Sets use the item value itself, and objects/Maps use their natural key order. The returned key can be a number, string, or an array of numbers/strings for composite sorting. Use {@link Aberdeen.invertString} on string keys for descending order. Returning `null` or `undefined` from `makeKey` will prevent the item from being rendered.
|
|
983
1981
|
*
|
|
984
1982
|
* @example Iterating an array
|
|
985
1983
|
* ```typescript
|
|
@@ -1038,9 +2036,9 @@ export function onEach<K extends string | number | symbol, T>(
|
|
|
1038
2036
|
*
|
|
1039
2037
|
* setTimeout(() => $tags.add('reactive'), 2000);
|
|
1040
2038
|
* ```
|
|
1041
|
-
* @see {@link invertString} To easily create keys for reverse sorting.
|
|
2039
|
+
* @see {@link Aberdeen.invertString} To easily create keys for reverse sorting.
|
|
1042
2040
|
*/
|
|
1043
|
-
|
|
2041
|
+
function onEach(
|
|
1044
2042
|
target: TargetType,
|
|
1045
2043
|
render: (value: any, index: any) => void,
|
|
1046
2044
|
makeKey?: (value: any, key: any) => SortKeyType,
|
|
@@ -1058,41 +2056,9 @@ function isObjEmpty(obj: object): boolean {
|
|
|
1058
2056
|
}
|
|
1059
2057
|
|
|
1060
2058
|
/** @private */
|
|
1061
|
-
|
|
2059
|
+
const EMPTY = Symbol("empty");
|
|
1062
2060
|
|
|
1063
|
-
|
|
1064
|
-
* Reactively checks if an observable array, object, Map, or Set is empty.
|
|
1065
|
-
*
|
|
1066
|
-
* This function not only returns the current emptiness state but also establishes
|
|
1067
|
-
* a reactive dependency. If the emptiness state of the `proxied` object or array
|
|
1068
|
-
* changes later (e.g., an item is added to an empty array, or the last property
|
|
1069
|
-
* is deleted from an object), the scope that called `isEmpty` will be automatically
|
|
1070
|
-
* scheduled for re-evaluation.
|
|
1071
|
-
*
|
|
1072
|
-
* @param proxied The observable array, object, Map, or Set to check.
|
|
1073
|
-
* @returns `true` if the array has length 0, the Map/Set has size 0, or the object has no own enumerable properties, `false` otherwise.
|
|
1074
|
-
*
|
|
1075
|
-
* @example
|
|
1076
|
-
* ```typescript
|
|
1077
|
-
* const $items = A.proxy([]);
|
|
1078
|
-
*
|
|
1079
|
-
* // Reactively display a message if the items array is empty
|
|
1080
|
-
* A('div', () => {
|
|
1081
|
-
* if (A.isEmpty($items)) {
|
|
1082
|
-
* A('p i#No items yet!');
|
|
1083
|
-
* } else {
|
|
1084
|
-
* A.onEach($items, item => A('p#'+item));
|
|
1085
|
-
* }
|
|
1086
|
-
* });
|
|
1087
|
-
*
|
|
1088
|
-
* // Adding an item will automatically remove the "No items yet!" message
|
|
1089
|
-
* setInterval(() => {
|
|
1090
|
-
* if (!$items.length || Math.random()>0.5) $items.push('Item');
|
|
1091
|
-
* else $items.length = 0;
|
|
1092
|
-
* }, 1000)
|
|
1093
|
-
* ```
|
|
1094
|
-
*/
|
|
1095
|
-
export function isEmpty(proxied: TargetType): boolean {
|
|
2061
|
+
function isEmpty(proxied: TargetType): boolean {
|
|
1096
2062
|
const target = (proxied as any)[TARGET_SYMBOL] || proxied;
|
|
1097
2063
|
const scope = currentScope;
|
|
1098
2064
|
|
|
@@ -1121,38 +2087,7 @@ export function isEmpty(proxied: TargetType): boolean {
|
|
|
1121
2087
|
return oldEmpty;
|
|
1122
2088
|
}
|
|
1123
2089
|
|
|
1124
|
-
|
|
1125
|
-
export interface ValueRef<T> {
|
|
1126
|
-
value: T;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
/**
|
|
1130
|
-
* Reactively counts the number of properties in an object.
|
|
1131
|
-
*
|
|
1132
|
-
* @param proxied The observable object to count. In case an `array`, `Map`, or `Set` is passed in, a {@link ref} to its `.length` or `.size` will be returned.
|
|
1133
|
-
* @returns an observable object for which the `value` property reflects the number of properties in `proxied` with a value other than `undefined`, or the collection size for arrays, Maps, and Sets.
|
|
1134
|
-
*
|
|
1135
|
-
* @example
|
|
1136
|
-
* ```typescript
|
|
1137
|
-
* const $items = A.proxy({x: 3, y: 7} as any);
|
|
1138
|
-
* const $count = A.count($items);
|
|
1139
|
-
*
|
|
1140
|
-
* // Create a DOM text node for the count:
|
|
1141
|
-
* A('div text=', $count);
|
|
1142
|
-
* // <div>2</div>
|
|
1143
|
-
|
|
1144
|
-
* // Or we can use it in an {@link derive} function:
|
|
1145
|
-
* A(() => console.log("The count is now", $count.value));
|
|
1146
|
-
* // The count is now 2
|
|
1147
|
-
*
|
|
1148
|
-
* // Adding/removing items will update the count
|
|
1149
|
-
* $items.z = 12;
|
|
1150
|
-
* // Asynchronously, after 0ms:
|
|
1151
|
-
* // <div>3</div>
|
|
1152
|
-
* // The count is now 3
|
|
1153
|
-
* ```
|
|
1154
|
-
*/
|
|
1155
|
-
export function count(proxied: TargetType): ValueRef<number> {
|
|
2090
|
+
function count(proxied: TargetType): ValueRef<number> {
|
|
1156
2091
|
if (proxied instanceof Array) return ref(proxied, "length");
|
|
1157
2092
|
if (proxied instanceof Map || proxied instanceof Set) return ref(proxied, "size");
|
|
1158
2093
|
|
|
@@ -1175,7 +2110,7 @@ export function count(proxied: TargetType): ValueRef<number> {
|
|
|
1175
2110
|
}
|
|
1176
2111
|
|
|
1177
2112
|
/** @internal */
|
|
1178
|
-
|
|
2113
|
+
function defaultEmitHandler(
|
|
1179
2114
|
target: TargetType,
|
|
1180
2115
|
index: string | symbol | number,
|
|
1181
2116
|
newData: any,
|
|
@@ -1521,34 +2456,17 @@ function optProxy(value: any): any {
|
|
|
1521
2456
|
return proxied;
|
|
1522
2457
|
}
|
|
1523
2458
|
|
|
1524
|
-
/**
|
|
1525
|
-
* When `proxy` is called with a Promise, the returned object has this shape.
|
|
1526
|
-
*/
|
|
1527
|
-
export interface PromiseProxy<T> {
|
|
1528
|
-
/**
|
|
1529
|
-
* True if the promise is still pending, false if it has resolved or rejected.
|
|
1530
|
-
*/
|
|
1531
|
-
busy: boolean;
|
|
1532
|
-
/**
|
|
1533
|
-
* If the promise has resolved, this contains the resolved value.
|
|
1534
|
-
*/
|
|
1535
|
-
value?: T;
|
|
1536
|
-
/**
|
|
1537
|
-
* If the promise has rejected, this contains the rejection error.
|
|
1538
|
-
*/
|
|
1539
|
-
error?: any;
|
|
1540
|
-
}
|
|
1541
2459
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
2460
|
+
function proxy<T extends any>(target: Promise<T>): PromiseProxy<T>;
|
|
2461
|
+
function proxy<T extends any>(target: Array<T>): Array<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T >;
|
|
2462
|
+
function proxy<T extends object>(target: T): T;
|
|
2463
|
+
function proxy<T extends any>(target: T): ValueRef<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T>;
|
|
1546
2464
|
|
|
1547
2465
|
/**
|
|
1548
2466
|
* Creates a reactive proxy around the given data.
|
|
1549
2467
|
*
|
|
1550
2468
|
* Reading properties from the returned proxy within a reactive scope (like one created by
|
|
1551
|
-
* {@link
|
|
2469
|
+
* {@link Aberdeen | A} or {@link Aberdeen.derive}) establishes a subscription. Modifying properties *through*
|
|
1552
2470
|
* the proxy will notify subscribed scopes, causing them to re-execute.
|
|
1553
2471
|
*
|
|
1554
2472
|
* - Plain objects, arrays, Maps, and Sets are wrapped in a standard JavaScript `Proxy` that intercepts
|
|
@@ -1560,7 +2478,7 @@ export function proxy<T extends any>(target: T): ValueRef<T extends number ? num
|
|
|
1560
2478
|
* is set to `false`. If the promise is rejected, `error` is set and `busy` is also
|
|
1561
2479
|
* set to `false`.
|
|
1562
2480
|
*
|
|
1563
|
-
* Use {@link unproxy} to get the original underlying data back.
|
|
2481
|
+
* Use {@link Aberdeen.unproxy} to get the original underlying data back.
|
|
1564
2482
|
* By convention in the examples below, local variables that hold proxied values are prefixed with `$`.
|
|
1565
2483
|
*
|
|
1566
2484
|
* @param target - The object, array, Map, Set, or primitive value to make reactive.
|
|
@@ -1609,7 +2527,7 @@ export function proxy<T extends any>(target: T): ValueRef<T extends number ? num
|
|
|
1609
2527
|
* setTimeout(() => $graph.grow(), 4000);
|
|
1610
2528
|
* ```
|
|
1611
2529
|
*/
|
|
1612
|
-
|
|
2530
|
+
function proxy(target: TargetType): TargetType {
|
|
1613
2531
|
if (target instanceof Promise) {
|
|
1614
2532
|
const result: PromiseProxy<any> = optProxy({
|
|
1615
2533
|
busy: true,
|
|
@@ -1630,41 +2548,7 @@ export function proxy(target: TargetType): TargetType {
|
|
|
1630
2548
|
);
|
|
1631
2549
|
}
|
|
1632
2550
|
|
|
1633
|
-
|
|
1634
|
-
* Returns the original, underlying data target from a reactive proxy created by {@link proxy}.
|
|
1635
|
-
* If the input `target` is not a proxy, it is returned directly.
|
|
1636
|
-
*
|
|
1637
|
-
* This is useful when you want to avoid triggering subscriptions during read operations or
|
|
1638
|
-
* re-executes during write operations. Using {@link peek} is an alternative way to achieve this.
|
|
1639
|
-
*
|
|
1640
|
-
* @param target - A proxied object, array, or any other value.
|
|
1641
|
-
* @returns The underlying (unproxied) data, or the input value if it wasn't a proxy.
|
|
1642
|
-
* @template T - The type of the target.
|
|
1643
|
-
*
|
|
1644
|
-
* @example
|
|
1645
|
-
* ```typescript
|
|
1646
|
-
* const $user = A.proxy({ name: 'Frank' });
|
|
1647
|
-
* const rawUser = A.unproxy($user);
|
|
1648
|
-
*
|
|
1649
|
-
* // Log reactively
|
|
1650
|
-
* A(() => console.log('proxied', $user.name));
|
|
1651
|
-
* // The following will only ever log once, as we're not subscribing to any observable
|
|
1652
|
-
* A(() => console.log('unproxied', rawUser.name));
|
|
1653
|
-
*
|
|
1654
|
-
* // This cause the first log to run again:
|
|
1655
|
-
* setTimeout(() => $user.name += '!', 1000);
|
|
1656
|
-
*
|
|
1657
|
-
* // This doesn't cause any new logs:
|
|
1658
|
-
* setTimeout(() => rawUser.name += '?', 2000);
|
|
1659
|
-
*
|
|
1660
|
-
* // Both $user and rawUser end up as `{name: 'Frank!?'}`
|
|
1661
|
-
* setTimeout(() => {
|
|
1662
|
-
* console.log('final proxied', $user)
|
|
1663
|
-
* console.log('final unproxied', rawUser)
|
|
1664
|
-
* }, 3000);
|
|
1665
|
-
* ```
|
|
1666
|
-
*/
|
|
1667
|
-
export function unproxy<T>(target: T): T {
|
|
2551
|
+
function unproxy<T>(target: T): T {
|
|
1668
2552
|
return target ? (target as any)[TARGET_SYMBOL] || target : target;
|
|
1669
2553
|
}
|
|
1670
2554
|
|
|
@@ -1679,7 +2563,7 @@ function destroyWithClass(element: Element, cls: string) {
|
|
|
1679
2563
|
|
|
1680
2564
|
/**
|
|
1681
2565
|
* Recursively copies properties or array items from `src` to `dst`.
|
|
1682
|
-
* It's designed to work efficiently with reactive proxies created by {@link proxy}.
|
|
2566
|
+
* It's designed to work efficiently with reactive proxies created by {@link Aberdeen.proxy}.
|
|
1683
2567
|
*
|
|
1684
2568
|
* - **Minimizes Updates:** When copying between objects/arrays (proxied or not), if a nested object
|
|
1685
2569
|
* exists in `dst` with the same constructor as the corresponding object in `src`, `copy`
|
|
@@ -1705,15 +2589,15 @@ function destroyWithClass(element: Element, cls: string) {
|
|
|
1705
2589
|
* ```
|
|
1706
2590
|
*/
|
|
1707
2591
|
|
|
1708
|
-
|
|
2592
|
+
function copy<T extends object>(dst: T, src: T): boolean;
|
|
1709
2593
|
/**
|
|
1710
2594
|
* Like above, but copies `src` into `dst[dstKey]`. This is useful if you're unsure if dst[dstKey]
|
|
1711
2595
|
* already exists (as the right type of object) or if you don't want to subscribe to dst[dstKey].
|
|
1712
2596
|
*
|
|
1713
2597
|
* @param dstKey - Optional key in `dst` to copy into.
|
|
1714
2598
|
*/
|
|
1715
|
-
|
|
1716
|
-
|
|
2599
|
+
function copy<T extends object>(dst: T, dstKey: keyof T, src: T[typeof dstKey]): boolean;
|
|
2600
|
+
function copy(a: any, b: any, c?: any): boolean {
|
|
1717
2601
|
if (arguments.length > 2) return copySet(a, b, c, 0);
|
|
1718
2602
|
return copyImpl(a, b, 0);
|
|
1719
2603
|
}
|
|
@@ -1730,24 +2614,9 @@ function copySet(dst: any, dstKey: any, src: any, flags: number): boolean {
|
|
|
1730
2614
|
return true;
|
|
1731
2615
|
}
|
|
1732
2616
|
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
*
|
|
1737
|
-
* @example Basic merge
|
|
1738
|
-
* ```typescript
|
|
1739
|
-
* const source = { b: { c: 99 }, d: undefined }; // d: undefined will delete
|
|
1740
|
-
* const $dest = A.proxy({ a: 1, b: { x: 5 }, d: 4 });
|
|
1741
|
-
* A.merge($dest, source);
|
|
1742
|
-
* A.merge($dest, 'b', { y: 6 }); // merge into $dest.b
|
|
1743
|
-
* A.merge($dest, 'c', { z: 7 }); // $dest.c doesn't exist yet, so it will just be assigned
|
|
1744
|
-
* console.log($dest); // proxy({ a: 1, b: { c: 99, x: 5, y: 6 }, c: { z: 7 } })
|
|
1745
|
-
* ```
|
|
1746
|
-
*
|
|
1747
|
-
*/
|
|
1748
|
-
export function merge<T extends object>(dst: T, value: Partial<T>): boolean;
|
|
1749
|
-
export function merge<T extends object>(dst: T, dstKey: keyof T, value: Partial<T[typeof dstKey]>): boolean;
|
|
1750
|
-
export function merge(a: any, b: any, c?: any) {
|
|
2617
|
+
function merge<T extends object>(dst: T, value: Partial<T>): boolean;
|
|
2618
|
+
function merge<T extends object>(dst: T, dstKey: keyof T, value: Partial<T[typeof dstKey]>): boolean;
|
|
2619
|
+
function merge(a: any, b: any, c?: any) {
|
|
1751
2620
|
if (arguments.length > 2) return copySet(a, b, c, MERGE);
|
|
1752
2621
|
return copyImpl(a, b, MERGE);
|
|
1753
2622
|
}
|
|
@@ -1900,91 +2769,19 @@ const MERGE = 1;
|
|
|
1900
2769
|
const COPY_SUBSCRIBE = 32;
|
|
1901
2770
|
const COPY_EMIT = 64;
|
|
1902
2771
|
|
|
1903
|
-
|
|
1904
|
-
* A symbol that controls how Aberdeen handles an object in copy operations and proxy wrapping.
|
|
1905
|
-
*
|
|
1906
|
-
* The **presence** of this symbol (regardless of its value) prevents deep-copying: the object is
|
|
1907
|
-
* stored and passed by reference in {@link clone} and {@link copy}.
|
|
1908
|
-
*
|
|
1909
|
-
* The **value** of the symbol controls proxy wrapping when the object is read from reactive state:
|
|
1910
|
-
* - **Truthy** (e.g. `true`): the object is fully opaque — it is not wrapped in a proxy, so its
|
|
1911
|
-
* properties are not observable. Use this for objects that break when proxied (e.g. class instances
|
|
1912
|
-
* with internal slots, Promises) or that must be invisible to Aberdeen's reactive system.
|
|
1913
|
-
* - **Falsy** (e.g. `false`): the object is still wrapped in a proxy, so reads on its properties
|
|
1914
|
-
* create reactive dependencies as normal — only deep-copying is suppressed.
|
|
1915
|
-
*/
|
|
1916
|
-
export const OPAQUE = Symbol("OPAQUE");
|
|
2772
|
+
const OPAQUE = Symbol("OPAQUE");
|
|
1917
2773
|
|
|
1918
|
-
|
|
1919
|
-
* Use {@link OPAQUE} instead. This is an alias kept for backward compatibility.
|
|
1920
|
-
*
|
|
1921
|
-
* @deprecated
|
|
1922
|
-
*/
|
|
1923
|
-
export const NO_COPY = OPAQUE;
|
|
2774
|
+
const NO_COPY = OPAQUE;
|
|
1924
2775
|
|
|
1925
2776
|
// Promises break when proxied, so mark them as fully opaque
|
|
1926
2777
|
(Promise.prototype as any)[OPAQUE] = true;
|
|
1927
2778
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
*
|
|
1931
|
-
* Any property you assign to `cssVars` becomes available as a CSS custom property throughout your application.
|
|
1932
|
-
*
|
|
1933
|
-
* Use {@link setSpacingCssVars} to optionally initialize `cssVars[1]` through `cssVars[12]` with an exponential spacing scale.
|
|
1934
|
-
*
|
|
1935
|
-
* When you reference a CSS variable in Aberdeen using the `$` prefix (e.g., `$primary`), it automatically resolves to `var(--primary)`.
|
|
1936
|
-
* For numeric keys (which can't be used directly as CSS custom property names), Aberdeen prefixes them with `m` (e.g., `$3` becomes `var(--m3)`).
|
|
1937
|
-
*
|
|
1938
|
-
* When you add the first property to cssVars, Aberdeen automatically creates a reactive `<style>` tag in `<head>`
|
|
1939
|
-
* containing the `:root` CSS custom property declarations. The style tag is automatically removed if cssVars becomes empty.
|
|
1940
|
-
*
|
|
1941
|
-
* @example
|
|
1942
|
-
* ```javascript
|
|
1943
|
-
* import A from 'aberdeen';
|
|
1944
|
-
*
|
|
1945
|
-
* // Optionally initialize spacing scale
|
|
1946
|
-
* A.setSpacingCssVars(); // Uses defaults: base=1, unit='rem'
|
|
1947
|
-
*
|
|
1948
|
-
* // Define custom colors - style tag is automatically created
|
|
1949
|
-
* A.cssVars.primary = '#3b82f6';
|
|
1950
|
-
* A.cssVars.danger = '#ef4444';
|
|
1951
|
-
*
|
|
1952
|
-
* // Use in elements with the $ prefix
|
|
1953
|
-
* A('button bg:$primary fg:white #I'm a primary button');
|
|
1954
|
-
*
|
|
1955
|
-
* // Use spacing (if setSpacingCssVars() was called)
|
|
1956
|
-
* A('hr mt:$3'); // Sets margin-top to var(--m3)
|
|
1957
|
-
* ```
|
|
1958
|
-
*/
|
|
1959
|
-
export const cssVars: Record<string, string> = optProxy({});
|
|
2779
|
+
// Also, DOM should not be proxied
|
|
2780
|
+
if (Node) (Node.prototype as any)[OPAQUE] = true;
|
|
1960
2781
|
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
* The scale is calculated as `2^(n-3) * base`, providing values from `0.25 * base` to `512 * base`.
|
|
1965
|
-
*
|
|
1966
|
-
* @param base - The base size for the spacing scale that will apply to `cssVars[3]`. Every step up the scale will double this, while every step down will halve it. Defaults to 1.
|
|
1967
|
-
* @param unit - The CSS unit to use, like 'rem', 'em', or 'px'. Defaults to 'rem'.
|
|
1968
|
-
*
|
|
1969
|
-
* @example
|
|
1970
|
-
* ```javascript
|
|
1971
|
-
* import A from 'aberdeen';
|
|
1972
|
-
* // Use default scale (0.25rem to 512rem)
|
|
1973
|
-
* A.setSpacingCssVars();
|
|
1974
|
-
*
|
|
1975
|
-
* // Use custom base size
|
|
1976
|
-
* A.setSpacingCssVars(16, 'px'); // 4px to 8192px
|
|
1977
|
-
*
|
|
1978
|
-
* // Use em units
|
|
1979
|
-
* A.setSpacingCssVars(1, 'em'); // 0.25em to 512em
|
|
1980
|
-
*
|
|
1981
|
-
* // Show the last generated spacing values
|
|
1982
|
-
* A.onEach(A.cssVars, (value, key) => {
|
|
1983
|
-
* A(`div #${key} → ${value}`)
|
|
1984
|
-
* }, (value, key) => parseInt(key)); // Numeric sort
|
|
1985
|
-
* ```
|
|
1986
|
-
*/
|
|
1987
|
-
export function setSpacingCssVars(base = 1, unit = 'rem'): void {
|
|
2782
|
+
const cssVars: Record<string, string> = optProxy({});
|
|
2783
|
+
|
|
2784
|
+
function setSpacingCssVars(base = 1, unit = 'rem'): void {
|
|
1988
2785
|
for (let i = 0; i <= 12; i++) {
|
|
1989
2786
|
cssVars[i] = 2 ** (i - 3) * base + unit;
|
|
1990
2787
|
}
|
|
@@ -2012,34 +2809,11 @@ function cssVarRef(value: string): string {
|
|
|
2012
2809
|
|
|
2013
2810
|
let darkModeState: {value: boolean} | undefined;
|
|
2014
2811
|
|
|
2015
|
-
|
|
2016
|
-
* Returns whether the user's browser prefers a dark color scheme.
|
|
2017
|
-
*
|
|
2018
|
-
* This function is reactive - scopes that call it will re-execute when the
|
|
2019
|
-
* browser's color scheme preference changes (via the `prefers-color-scheme` media query).
|
|
2020
|
-
*
|
|
2021
|
-
* Use this in combination with {@link A | A} and {@link cssVars} to implement theme switching:
|
|
2022
|
-
*
|
|
2023
|
-
* @returns `true` if the browser prefers dark mode, `false` if it prefers light mode.
|
|
2024
|
-
*
|
|
2025
|
-
* @example
|
|
2026
|
-
* ```javascript
|
|
2027
|
-
* import A from 'aberdeen';
|
|
2028
|
-
*
|
|
2029
|
-
* // Reactively set colors based on browser preference
|
|
2030
|
-
* A(() => {
|
|
2031
|
-
* A.cssVars.bg = A.darkMode() ? '#1a1a1a' : '#ffffff';
|
|
2032
|
-
* A.cssVars.fg = A.darkMode() ? '#e5e5e5' : '#000000';
|
|
2033
|
-
* });
|
|
2034
|
-
*
|
|
2035
|
-
* A('div bg:$bg fg:$fg p:1rem #Colors change based on system dark mode preference');
|
|
2036
|
-
* ```
|
|
2037
|
-
*/
|
|
2038
|
-
export function darkMode(): boolean {
|
|
2812
|
+
function darkMode(): boolean {
|
|
2039
2813
|
if (!darkModeState) {
|
|
2040
2814
|
// Initialize on first use
|
|
2041
2815
|
|
|
2042
|
-
if (
|
|
2816
|
+
if (!window || !window.matchMedia) return false;
|
|
2043
2817
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
2044
2818
|
|
|
2045
2819
|
darkModeState = proxy({ value: mediaQuery.matches });
|
|
@@ -2071,14 +2845,7 @@ function cloneValue(v: any, flags: number): any {
|
|
|
2071
2845
|
return typeof v === "object" && v !== null ? cloneRecursive(v, flags) : v;
|
|
2072
2846
|
}
|
|
2073
2847
|
|
|
2074
|
-
|
|
2075
|
-
* Clone an (optionally proxied) object or array.
|
|
2076
|
-
*
|
|
2077
|
-
* @param src The object or array to clone. If it is proxied, `clone` will subscribe to any changes to the (nested) data structure.
|
|
2078
|
-
* @template T - The type of the objects being copied.
|
|
2079
|
-
* @returns A new unproxied array or object (of the same type as `src`), containing a deep copy of `src`.
|
|
2080
|
-
*/
|
|
2081
|
-
export function clone<T extends object>(src: T): T {
|
|
2848
|
+
function clone<T extends object>(src: T): T {
|
|
2082
2849
|
if (typeof src !== 'object' || src === null) return src;
|
|
2083
2850
|
let flags = 0;
|
|
2084
2851
|
const unproxied = (src as any)[TARGET_SYMBOL];
|
|
@@ -2112,35 +2879,7 @@ const refHandler: ProxyHandler<RefTarget> = {
|
|
|
2112
2879
|
},
|
|
2113
2880
|
};
|
|
2114
2881
|
|
|
2115
|
-
|
|
2116
|
-
* Creates a reactive reference (`{ value: T }`-like object) to a specific value
|
|
2117
|
-
* within a proxied object or array.
|
|
2118
|
-
*
|
|
2119
|
-
* This is primarily used for the `bind` property in {@link A | A} to create two-way data bindings
|
|
2120
|
-
* with form elements, and for passing a reactive property to any of the {@link A | A} key-value pairs.
|
|
2121
|
-
*
|
|
2122
|
-
* Reading `ref.value` accesses the property from the underlying proxy (and subscribes the current scope).
|
|
2123
|
-
* Assigning to `ref.value` updates the property in the underlying proxy (triggering reactive updates).
|
|
2124
|
-
*
|
|
2125
|
-
* @param target - The reactive proxy (created by {@link proxy}) containing the target property.
|
|
2126
|
-
* @param index - The key (for objects) or index (for arrays) of the property to reference.
|
|
2127
|
-
* @returns A reference object with a `value` property linked to the specified proxy property.
|
|
2128
|
-
*
|
|
2129
|
-
* @example
|
|
2130
|
-
* ```javascript
|
|
2131
|
-
* const $formData = A.proxy({ color: 'orange', velocity: 42 });
|
|
2132
|
-
*
|
|
2133
|
-
* // Usage with `bind`
|
|
2134
|
-
* A('input type=text bind=', A.ref($formData, 'color'));
|
|
2135
|
-
*
|
|
2136
|
-
* // Usage as a dynamic property, causes a TextNode with just the name to be created and live-updated
|
|
2137
|
-
* A('p text="Selected color: " text=', A.ref($formData, 'color'), 'color:', A.ref($formData, 'color'));
|
|
2138
|
-
*
|
|
2139
|
-
* // Changes are actually stored in $formData - this causes logs like `{color: "Blue", velocity 42}`
|
|
2140
|
-
* A(() => console.log($formData))
|
|
2141
|
-
* ```
|
|
2142
|
-
*/
|
|
2143
|
-
export function ref<T extends TargetType, K extends keyof T>(
|
|
2882
|
+
function ref<T extends TargetType, K extends keyof T>(
|
|
2144
2883
|
target: T,
|
|
2145
2884
|
index: K,
|
|
2146
2885
|
): ValueRef<T[K]> {
|
|
@@ -2263,18 +3002,6 @@ const SPECIAL_PROPS: { [key: string]: (el: Element, value: any) => void } = {
|
|
|
2263
3002
|
},
|
|
2264
3003
|
};
|
|
2265
3004
|
|
|
2266
|
-
/**
|
|
2267
|
-
* Make the `create` and `destroy` special properties no-ops.
|
|
2268
|
-
*
|
|
2269
|
-
* This is useful from within automated testing environments, where the transitioning
|
|
2270
|
-
* new and lingering old elements may make writing reliable selectors difficult.
|
|
2271
|
-
*
|
|
2272
|
-
* As this is only intended for testing, there's no way to re-enable the special properties
|
|
2273
|
-
* once disabled.
|
|
2274
|
-
*/
|
|
2275
|
-
export function disableCreateDestroy() {
|
|
2276
|
-
SPECIAL_PROPS.create = SPECIAL_PROPS.destroy = () => {};
|
|
2277
|
-
}
|
|
2278
3005
|
|
|
2279
3006
|
/**
|
|
2280
3007
|
* The core function for building reactive user interfaces in Aberdeen. It creates and inserts new DOM elements
|
|
@@ -2304,7 +3031,7 @@ export function disableCreateDestroy() {
|
|
|
2304
3031
|
* - A key followed by a ':' character (with no value), in which case the value is expected as a separate argument. The value is treated as a CSS value to be set inline on the *current* element. Example: `A('div margin-top:', someValueInPx)`. In case the value is a proxied object, its `.value` property will be applied reactively without needing to rerender the parent scope.
|
|
2305
3032
|
*
|
|
2306
3033
|
* ### Function arguments
|
|
2307
|
-
* When a function (without arguments nor a return value) is passed in, it will be reactively executed in its own observer scope, preserving the *current* element. So any `A()` invocations within this function will add child elements to or set properties on that element. 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 `A`, have been cleaned - see also {@link clean}).
|
|
3034
|
+
* When a function (without arguments nor a return value) is passed in, it will be reactively executed in its own observer scope, preserving the *current* element. So any `A()` invocations within this function will add child elements to or set properties on that element. 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 `A`, have been cleaned - see also {@link Aberdeen.clean}).
|
|
2308
3035
|
*
|
|
2309
3036
|
* ### Object arguments
|
|
2310
3037
|
* When an object is passed in, its key-value pairs are used to modify the *current* element according to the *Property rules* below, *unless* the key starts with a `$` character, in which case that character is stripped of and the key/value pair is treated as a CSS property, subject to the *CSS shortcuts* below. In case a value is a proxied object, its `.value` property will be applied reactively without needing to rerender the parent scope. In most cases, the string notation (`key=` and `key:`) is preferred over this object notation, for readability.
|
|
@@ -2319,7 +3046,7 @@ export function disableCreateDestroy() {
|
|
|
2319
3046
|
* - **Conditional CSS class:** If the key starts with a `.` character, its either added to or removed from the *current* element as a CSS class, based on the truthiness of the value. So `A('.hidden=', isHidden)` would toggle the `hidden` CSS class. This only works if the `=` is the last character of the string, and the next argument is the value. Its common for the value to be a proxied object, in which case its `.value` is reactively applied without needing to rerender the parent scope.
|
|
2320
3047
|
* - **Create transition:** When the key is `"create"`, the value will be added as a CSS class to the *current* element immediately, and then removed right after the browser has finished doing a layout pass. This behavior only triggers when the scope setting the `create` is the top-level scope being (re-)run. This allows for creation transitions, without triggering the transitions for deeply nested elements being drawn as part of a larger component. The string may also contain multiple dot-separated CSS classes, such as `.fade.grow`. The initial dot is optional. Alternatively, to allow for more complex transitions, the value may be a function that receives the `HTMLElement` being created as its only argument. It is *only* called if this is the top-level element being created in this scope run. See `transitions.ts` in the Aberdeen source code for some examples.
|
|
2321
3048
|
* - **Destroy transition:** When the key is `"destroy"` the value will be used to apply a CSS transition if the *current* element is later on removed from the DOM and is the top-level element to be removed. This happens as follows: actual removal from the DOM is delayed by 2 seconds, and in the mean-time the value string is added as a CSS class to the element, allowing for a deletion transition. The string may also contain multiple dot-separated CSS classes, such as `.fade.shrink`. The initial dot is optional. Alternatively, to allow for more complex transitions, the value may be a function that receives the `HTMLElement` to be removed from the DOM as its only argument. This function may perform any transitions and is then itself responsible for eventually removing the element from the DOM. See `transitions.ts` in the Aberdeen source code for some examples.
|
|
2322
|
-
* - **Two-way data binding:** When the key is `"bind"` a two-way binding between the `.value` property of the given proxied object, and the *current* input element (`<input>`, `<select>` or `<textarea>`) is created. This is often used together with {@link ref}, in order to use properties other than `.value`.
|
|
3049
|
+
* - **Two-way data binding:** When the key is `"bind"` a two-way binding between the `.value` property of the given proxied object, and the *current* input element (`<input>`, `<select>` or `<textarea>`) is created. This is often used together with {@link Aberdeen.ref}, in order to use properties other than `.value`.
|
|
2323
3050
|
* - **Text:**: If the key is `"text"`, the value will be appended as a `TextNode` to the *current* element. The same can also be done with the `#` syntax in string arguments, though `text=` allows additional properties to come after in the same string: `A('button text=Hello click=', alert)`.
|
|
2324
3051
|
* - **Unsafe HTML:** When the key is `"html"`, the value will be added as HTML to the *current* element. This should only be used in exceptional situations. Beware of XSS! Never use this with untrusted user data.
|
|
2325
3052
|
* - **Rich text:** When the key is `"rich"`, the value is parsed as simple markdown-like syntax and rendered as inline elements. Supports `*italic*`, `**bold**`, `` `code` ``, and `[link text](/path)`. All text content is safely escaped, making it suitable for user data (though links should be validated if untrusted). Example: `A('p rich="Click *here* for **more** info")`.
|
|
@@ -2337,7 +3064,7 @@ export function disableCreateDestroy() {
|
|
|
2337
3064
|
* | `fg` | `color` |
|
|
2338
3065
|
* | `r` | `border-radius` |
|
|
2339
3066
|
*
|
|
2340
|
-
* Also, when the value is a string starting with `$`, it is treated as a reference to a CSS variable, expanding to `var(--variableName)`. For numeric variable names (which can't be used directly as CSS custom property names), Aberdeen prefixes them with `m`, so `$3` expands to `var(--m3)`. This is primarily intended for use with {@link setSpacingCssVars}, which initializes spacing variables named `0` through `12` with an exponential spacing scale.
|
|
3067
|
+
* Also, when the value is a string starting with `$`, it is treated as a reference to a CSS variable, expanding to `var(--variableName)`. For numeric variable names (which can't be used directly as CSS custom property names), Aberdeen prefixes them with `m`, so `$3` expands to `var(--m3)`. This is primarily intended for use with {@link Aberdeen.setSpacingCssVars}, which initializes spacing variables named `0` through `12` with an exponential spacing scale.
|
|
2341
3068
|
*
|
|
2342
3069
|
* @returns The most inner DOM element that was created (not counting text nodes nor elements created by content functions), or the current element if no new element was created. You should normally not need to use the return value - use this function's DOM manipulation abilities instead. One valid use case is when integrating with non-Aberdeen code that requires a reference to a DOM element.
|
|
2343
3070
|
*
|
|
@@ -2393,10 +3120,10 @@ export function disableCreateDestroy() {
|
|
|
2393
3120
|
* A('p text="The color is " text=', $myColor, 'click=', () => $myColor.value = 'yellow')
|
|
2394
3121
|
* // Clicking the text will cause it to change color without recreating the <p> itself
|
|
2395
3122
|
* ```
|
|
2396
|
-
* This is often used together with {@link ref}, in order to use properties other than `.value`.
|
|
3123
|
+
* This is often used together with {@link Aberdeen.ref}, in order to use properties other than `.value`.
|
|
2397
3124
|
*/
|
|
2398
3125
|
|
|
2399
|
-
|
|
3126
|
+
function A(...args: any[]): undefined | Element {
|
|
2400
3127
|
let el: undefined | Element = currentScope.el;
|
|
2401
3128
|
let svg: boolean = currentScope.svg
|
|
2402
3129
|
|
|
@@ -2526,75 +3253,7 @@ function findFirst(str: string, chars: string, startPos: number): number {
|
|
|
2526
3253
|
|
|
2527
3254
|
let cssCount = 0;
|
|
2528
3255
|
|
|
2529
|
-
|
|
2530
|
-
* Inserts CSS rules into the document, scoping them with a unique class name.
|
|
2531
|
-
*
|
|
2532
|
-
* The `style` parameter can be either:
|
|
2533
|
-
* - A **concise style string** (for rules applying to the root class).
|
|
2534
|
-
* - An **object** where keys are selectors (with `&` representing the root class)
|
|
2535
|
-
* and values are concise style strings or nested objects. When the key does not contain `&`,
|
|
2536
|
-
* it is treated as a descendant selector. So `{p: "color:red"}` becomes `".AbdStlX p { color: red; }"` with `AbdStlX` being the generated class name.
|
|
2537
|
-
*
|
|
2538
|
-
* ### Concise Style Strings
|
|
2539
|
-
*
|
|
2540
|
-
* Concise style strings use two syntaxes (same as inline CSS in {@link A | A}):
|
|
2541
|
-
* - **Short form** `key:value` (no space after colon): The value ends at the next whitespace.
|
|
2542
|
-
* Example: `'m:$3 bg:red r:8px'`
|
|
2543
|
-
* - **Long form** `key: value;` (space after colon): The value continues until a semicolon.
|
|
2544
|
-
* Example: `'box-shadow: 2px 0 6px black; transition: all 0.3s ease;'`
|
|
2545
|
-
*
|
|
2546
|
-
* Both forms can be mixed: `'m:$3 box-shadow: 0 2px 4px rgba(0,0,0,0.2); bg:$cardBg'`
|
|
2547
|
-
*
|
|
2548
|
-
* Supports the same CSS shortcuts as {@link A | A} and CSS variable references with `$` (e.g., `$primary`, `$3`).
|
|
2549
|
-
*
|
|
2550
|
-
* @param style - A concise style string or a style object.
|
|
2551
|
-
* @returns The unique class name prefix used for scoping (e.g., `.AbdStl1`).
|
|
2552
|
-
* Use this prefix with {@link A | A} to apply the styles.
|
|
2553
|
-
*
|
|
2554
|
-
* @example Basic Usage with Shortcuts and CSS Variables
|
|
2555
|
-
* ```typescript
|
|
2556
|
-
* const cardClass = A.insertCss({
|
|
2557
|
-
* '&': 'bg:white p:$4 r:8px transition: background-color 0.3s;',
|
|
2558
|
-
* '&:hover': 'bg:#f5f5f5',
|
|
2559
|
-
* });
|
|
2560
|
-
*
|
|
2561
|
-
* A('section', cardClass, () => {
|
|
2562
|
-
* A('p#Card content');
|
|
2563
|
-
* });
|
|
2564
|
-
* ```
|
|
2565
|
-
*
|
|
2566
|
-
* @example Nested Selectors and Media Queries
|
|
2567
|
-
* ```typescript
|
|
2568
|
-
* const formClass = A.insertCss({
|
|
2569
|
-
* '&': 'bg:#0004 p:$3 r:$2',
|
|
2570
|
-
* button: {
|
|
2571
|
-
* '&': 'bg:$primary fg:white p:$2 r:4px cursor:pointer',
|
|
2572
|
-
* '&:hover': 'bg:$primaryHover',
|
|
2573
|
-
* '&:disabled': 'bg:#ccc cursor:not-allowed',
|
|
2574
|
-
* '.icon': 'display:inline-block mr:$1',
|
|
2575
|
-
* '@media (max-width: 600px)': 'p:$1 font-size:14px'
|
|
2576
|
-
* }
|
|
2577
|
-
* });
|
|
2578
|
-
*
|
|
2579
|
-
* A('form', formClass, () => {
|
|
2580
|
-
* A('button', () => {
|
|
2581
|
-
* A('span.icon text=🔥');
|
|
2582
|
-
* A('#Click Me');
|
|
2583
|
-
* });
|
|
2584
|
-
* });
|
|
2585
|
-
* ```
|
|
2586
|
-
*
|
|
2587
|
-
* @example Complex CSS Values
|
|
2588
|
-
* ```typescript
|
|
2589
|
-
* const badge = A.insertCss({
|
|
2590
|
-
* '&::before': 'content: "★"; color:gold mr:$1',
|
|
2591
|
-
* '&': 'position:relative box-shadow: 0 2px 8px rgba(0,0,0,0.15);'
|
|
2592
|
-
* });
|
|
2593
|
-
*
|
|
2594
|
-
* A(badge + ' span#Product Name');
|
|
2595
|
-
* ```
|
|
2596
|
-
*/
|
|
2597
|
-
export function insertCss(style: string | object): string {
|
|
3256
|
+
function insertCss(style: string | object): string {
|
|
2598
3257
|
const prefix = `.AbdStl${++cssCount}`;
|
|
2599
3258
|
const css = typeof style === 'string' ? styleStringToCss(style, prefix) : objectToCss(style, prefix);
|
|
2600
3259
|
if (css) {
|
|
@@ -2688,65 +3347,7 @@ function styleStringToCss(styleStr: string, selector: string): string {
|
|
|
2688
3347
|
return props ? `${selector}{${props}}\n` : "";
|
|
2689
3348
|
}
|
|
2690
3349
|
|
|
2691
|
-
|
|
2692
|
-
* Inserts CSS rules globally (unscoped).
|
|
2693
|
-
*
|
|
2694
|
-
* Works exactly like {@link insertCss}, but without prefixing selectors with a unique class name.
|
|
2695
|
-
* This is useful for global resets, base styles, or styles that need to apply to the entire document.
|
|
2696
|
-
*
|
|
2697
|
-
* Accepts the same concise style string syntax and CSS shortcuts as {@link insertCss}.
|
|
2698
|
-
* See {@link insertCss} for detailed documentation on syntax and shortcuts.
|
|
2699
|
-
*
|
|
2700
|
-
* @param style - Object with selectors as keys and concise CSS strings as values.
|
|
2701
|
-
*
|
|
2702
|
-
* @example Global Reset and Base Styles
|
|
2703
|
-
* ```typescript
|
|
2704
|
-
* // Set up global styles using CSS shortcuts
|
|
2705
|
-
* A.insertGlobalCss({
|
|
2706
|
-
* "*": "m:0 p:0 box-sizing:border-box",
|
|
2707
|
-
* "body": "font-family: system-ui, sans-serif; m:0 p:$3 bg:#434 fg:#d0dafa",
|
|
2708
|
-
* "a": "text-decoration:none fg:#57f",
|
|
2709
|
-
* "a:hover": "text-decoration:underline",
|
|
2710
|
-
* "code": "font-family:monospace bg:#222 fg:#afc p:4px r:3px"
|
|
2711
|
-
* });
|
|
2712
|
-
*
|
|
2713
|
-
* A('h2#Title without margins');
|
|
2714
|
-
* A('a#This is a link');
|
|
2715
|
-
* A('code#const x = 42;');
|
|
2716
|
-
* ```
|
|
2717
|
-
*
|
|
2718
|
-
* @example Responsive Global Styles
|
|
2719
|
-
* ```typescript
|
|
2720
|
-
* A.insertGlobalCss({
|
|
2721
|
-
* "html": "font-size:16px",
|
|
2722
|
-
* "body": "line-height:1.6",
|
|
2723
|
-
* "h1, h2, h3": "font-weight:600 mt:$4 mb:$2",
|
|
2724
|
-
* "@media (max-width: 768px)": {
|
|
2725
|
-
* "html": "font-size:14px",
|
|
2726
|
-
* "body": "p:$2"
|
|
2727
|
-
* },
|
|
2728
|
-
* "@media (prefers-color-scheme: dark)": {
|
|
2729
|
-
* "body": "bg:#1a1a1a fg:#e5e5e5",
|
|
2730
|
-
* "code": "bg:#2a2a2a"
|
|
2731
|
-
* }
|
|
2732
|
-
* });
|
|
2733
|
-
* ```
|
|
2734
|
-
* At-rules such as `@media` and `@keyframes` should use nested objects. For keyframes,
|
|
2735
|
-
* the step selectors (`0%`, `50%`, `100%`, etc.) become the nested keys and each value
|
|
2736
|
-
* should be a concise CSS declaration string.
|
|
2737
|
-
*
|
|
2738
|
-
* @example Animation Keyframes
|
|
2739
|
-
* ```typescript
|
|
2740
|
-
* A.insertGlobalCss({
|
|
2741
|
-
* "@keyframes connection-pulse": {
|
|
2742
|
-
* "0%": "box-shadow: inset 0 0 0 0 rgba(255, 70, 70, 0.4);",
|
|
2743
|
-
* "50%": "box-shadow: inset 0 0 0 6px rgba(255, 70, 70, 0.08);",
|
|
2744
|
-
* "100%": "box-shadow: inset 0 0 0 0 rgba(255, 70, 70, 0.4);"
|
|
2745
|
-
* }
|
|
2746
|
-
* });
|
|
2747
|
-
* ```
|
|
2748
|
-
*/
|
|
2749
|
-
export function insertGlobalCss(style: object) {
|
|
3350
|
+
function insertGlobalCss(style: object) {
|
|
2750
3351
|
const css = objectToCss(style, "");
|
|
2751
3352
|
if (css) {
|
|
2752
3353
|
let cnt = cssSnippetCount++;
|
|
@@ -2846,49 +3447,7 @@ function defaultOnError(error: Error) {
|
|
|
2846
3447
|
}
|
|
2847
3448
|
let onError: (error: Error) => boolean | undefined = defaultOnError;
|
|
2848
3449
|
|
|
2849
|
-
|
|
2850
|
-
* Sets a custom error handler function for errors that occur asynchronously
|
|
2851
|
-
* within reactive scopes (e.g., during updates triggered by proxy changes in
|
|
2852
|
-
* {@link derive} or {@link A | A} render functions).
|
|
2853
|
-
*
|
|
2854
|
-
* The default handler logs the error to `console.error` and adds a simple
|
|
2855
|
-
* 'Error' message div to the DOM at the location where the error occurred (if possible).
|
|
2856
|
-
*
|
|
2857
|
-
* Your handler can provide custom logging, UI feedback, or suppress the default
|
|
2858
|
-
* error message.
|
|
2859
|
-
*
|
|
2860
|
-
* @param handler - A function that accepts the `Error` object.
|
|
2861
|
-
* - Return `false` to prevent adding an error message to the DOM.
|
|
2862
|
-
* - Return `true` or `undefined` (or throw) to allow the error messages to be added to the DOM.
|
|
2863
|
-
*
|
|
2864
|
-
* @example Custom Logging and Suppressing Default Message
|
|
2865
|
-
* ```typescript
|
|
2866
|
-
* A.setErrorHandler(error => {
|
|
2867
|
-
* console.warn('Aberdeen render error:', error.message);
|
|
2868
|
-
* // Log to error reporting service
|
|
2869
|
-
* // myErrorReporter.log(error);
|
|
2870
|
-
*
|
|
2871
|
-
* try {
|
|
2872
|
-
* // Attempt to show a custom message in the UI
|
|
2873
|
-
* A('div#Oops, something went wrong!', errorClass);
|
|
2874
|
-
* } catch (e) {
|
|
2875
|
-
* // Ignore errors during error handling itself
|
|
2876
|
-
* }
|
|
2877
|
-
*
|
|
2878
|
-
* return false; // Suppress default console log and DOM error message
|
|
2879
|
-
* });
|
|
2880
|
-
*
|
|
2881
|
-
* // Styling for our custom error message
|
|
2882
|
-
* const errorClass = A.insertCss('background-color:#e31f00 display:inline-block color:white r:3px padding: 2px 4px;');
|
|
2883
|
-
*
|
|
2884
|
-
* // Cause an error within a render scope.
|
|
2885
|
-
* A('div.box', () => {
|
|
2886
|
-
* // Will cause our error handler to insert an error message within the box
|
|
2887
|
-
* noSuchFunction();
|
|
2888
|
-
* })
|
|
2889
|
-
* ```
|
|
2890
|
-
*/
|
|
2891
|
-
export function setErrorHandler(
|
|
3450
|
+
function setErrorHandler(
|
|
2892
3451
|
handler?: (error: Error) => boolean | undefined,
|
|
2893
3452
|
) {
|
|
2894
3453
|
onError = handler || defaultOnError;
|
|
@@ -2901,8 +3460,8 @@ export function setErrorHandler(
|
|
|
2901
3460
|
* This is useful for releasing resources, removing manual event listeners, or cleaning up
|
|
2902
3461
|
* side effects associated with the scope. Cleaners are run in reverse order of registration.
|
|
2903
3462
|
*
|
|
2904
|
-
* Scopes are created by functions like {@link derive}, {@link mount}, {@link
|
|
2905
|
-
* and internally by constructs like {@link onEach}.
|
|
3463
|
+
* Scopes are created by functions like {@link Aberdeen.derive}, {@link Aberdeen.mount}, {@link Aberdeen | A} (when given a render function),
|
|
3464
|
+
* and internally by constructs like {@link Aberdeen.onEach}.
|
|
2906
3465
|
*
|
|
2907
3466
|
* @param cleaner - The function to execute during cleanup.
|
|
2908
3467
|
*
|
|
@@ -2932,79 +3491,31 @@ export function setErrorHandler(
|
|
|
2932
3491
|
* ```
|
|
2933
3492
|
*/
|
|
2934
3493
|
|
|
2935
|
-
|
|
3494
|
+
function clean(cleaner: () => void) {
|
|
2936
3495
|
currentScope.cleaners.push(cleaner);
|
|
2937
3496
|
}
|
|
2938
3497
|
|
|
2939
|
-
|
|
2940
|
-
* Creates a reactive scope that automatically re-executes the provided function
|
|
2941
|
-
* whenever any proxied data (created by {@link proxy}) read during its last execution changes, storing
|
|
2942
|
-
* its return value in an observable.
|
|
2943
|
-
*
|
|
2944
|
-
* Updates are batched and run asynchronously shortly after the changes occur.
|
|
2945
|
-
* Use {@link clean} to register cleanup logic for the scope.
|
|
2946
|
-
* Use {@link peek} or {@link unproxy} within the function to read proxied data without subscribing to it.
|
|
2947
|
-
*
|
|
2948
|
-
* @param func - The function to execute reactively. Any DOM manipulations should typically
|
|
2949
|
-
* be done using {@link A | A} within this function. Its return value will be made available as an
|
|
2950
|
-
* observable returned by the `derive()` function.
|
|
2951
|
-
* @returns An observable object, with its `value` property containing whatever the last run of `func` returned.
|
|
2952
|
-
*
|
|
2953
|
-
* @example Observation creating UI components
|
|
2954
|
-
* ```typescript
|
|
2955
|
-
* const $data = A.proxy({ user: 'Frank', notifications: 42 });
|
|
2956
|
-
*
|
|
2957
|
-
* A('main', () => {
|
|
2958
|
-
* console.log('Welcome');
|
|
2959
|
-
* A('h3#Welcome, ' + $data.user); // Reactive text
|
|
2960
|
-
*
|
|
2961
|
-
* A.derive(() => {
|
|
2962
|
-
* // When $data.notifications changes, only this inner scope reruns,
|
|
2963
|
-
* // leaving the `<p>Welcome, ..</p>` untouched.
|
|
2964
|
-
* console.log('Notifications');
|
|
2965
|
-
* A('code.notification-badge text=', $data.notifications);
|
|
2966
|
-
* A('a text=Notify! click=', () => $data.notifications++);
|
|
2967
|
-
* });
|
|
2968
|
-
* });
|
|
2969
|
-
* ```
|
|
2970
|
-
*
|
|
2971
|
-
* ***Note*** that the above could just as easily be done using `A(func)` instead of `derive(func)`.
|
|
2972
|
-
*
|
|
2973
|
-
* @example Observation with return value
|
|
2974
|
-
* ```typescript
|
|
2975
|
-
* const $counter = A.proxy(0);
|
|
2976
|
-
* setInterval(() => $counter.value++, 1000);
|
|
2977
|
-
* const $double = A.derive(() => $counter.value * 2);
|
|
2978
|
-
*
|
|
2979
|
-
* A('h3', () => {
|
|
2980
|
-
* A(`#counter=${$counter.value} double=${$double.value}`);
|
|
2981
|
-
* })
|
|
2982
|
-
* ```
|
|
2983
|
-
*
|
|
2984
|
-
* @overload
|
|
2985
|
-
* @param func Func without a return value.
|
|
2986
|
-
*/
|
|
2987
|
-
export function derive<T>(func: () => T): ValueRef<T> {
|
|
3498
|
+
function derive<T>(func: () => T): ValueRef<T> {
|
|
2988
3499
|
return new ResultScope<T>(func).result;
|
|
2989
3500
|
}
|
|
2990
3501
|
|
|
2991
3502
|
/**
|
|
2992
3503
|
* Attaches a reactive Aberdeen UI fragment to an existing DOM element. Without the use of
|
|
2993
|
-
* this function, {@link
|
|
3504
|
+
* this function, {@link Aberdeen | A} will assume `document.body` as its root.
|
|
2994
3505
|
*
|
|
2995
3506
|
* It creates a top-level reactive scope associated with the `parentElement`. The provided
|
|
2996
3507
|
* function `func` is executed immediately within this scope. Any proxied data read by `func`
|
|
2997
3508
|
* will cause it to re-execute when the data changes, updating the DOM elements created within it.
|
|
2998
3509
|
*
|
|
2999
|
-
* Calls to {@link
|
|
3000
|
-
* You can nest {@link derive} or other {@link
|
|
3001
|
-
* Use {@link unmountAll} to clean up all mounted scopes and their DOM nodes.
|
|
3510
|
+
* Calls to {@link Aberdeen | A} inside `func` will append nodes to `parentElement`.
|
|
3511
|
+
* You can nest {@link Aberdeen.derive} or other {@link Aberdeen | A} scopes within `func`.
|
|
3512
|
+
* Use {@link Aberdeen.unmountAll} to clean up all mounted scopes and their DOM nodes.
|
|
3002
3513
|
*
|
|
3003
3514
|
* Mounting scopes happens reactively, meaning that if this function is called from within another
|
|
3004
|
-
* ({@link derive} or {@link
|
|
3515
|
+
* ({@link Aberdeen.derive} or {@link Aberdeen | A} or {@link Aberdeen.mount}) scope that gets cleaned up, so will the mount.
|
|
3005
3516
|
*
|
|
3006
3517
|
* @param parentElement - The native DOM `Element` to which the UI fragment will be appended.
|
|
3007
|
-
* @param func - The function that defines the UI fragment, typically containing calls to {@link
|
|
3518
|
+
* @param func - The function that defines the UI fragment, typically containing calls to {@link Aberdeen | A}.
|
|
3008
3519
|
*
|
|
3009
3520
|
* @example Basic Mount
|
|
3010
3521
|
* ```javascript
|
|
@@ -3031,18 +3542,11 @@ export function derive<T>(func: () => T): ValueRef<T> {
|
|
|
3031
3542
|
* Note how the inner mount behaves reactively as well, automatically unmounting when it's parent observer scope re-runs.
|
|
3032
3543
|
*/
|
|
3033
3544
|
|
|
3034
|
-
|
|
3545
|
+
function mount(parentElement: Element, func: () => void) {
|
|
3035
3546
|
new MountScope(parentElement, func);
|
|
3036
3547
|
}
|
|
3037
3548
|
|
|
3038
|
-
|
|
3039
|
-
* Removes all Aberdeen-managed DOM nodes and stops all active reactive scopes
|
|
3040
|
-
* (created by {@link mount}, {@link derive}, {@link A | A} with functions, etc.).
|
|
3041
|
-
*
|
|
3042
|
-
* This effectively cleans up the entire Aberdeen application state. Aside from in
|
|
3043
|
-
* automated tests, there should probably be little reason to call this function.
|
|
3044
|
-
*/
|
|
3045
|
-
export function unmountAll() {
|
|
3549
|
+
function unmountAll() {
|
|
3046
3550
|
ROOT_SCOPE.remove();
|
|
3047
3551
|
cssCount = 0;
|
|
3048
3552
|
}
|
|
@@ -3050,10 +3554,10 @@ export function unmountAll() {
|
|
|
3050
3554
|
/**
|
|
3051
3555
|
* Executes a function or retrieves a value *without* creating subscriptions in the current reactive scope, and returns its result.
|
|
3052
3556
|
*
|
|
3053
|
-
* This is useful when you need to access reactive data inside a reactive scope (like {@link
|
|
3557
|
+
* This is useful when you need to access reactive data inside a reactive scope (like {@link Aberdeen | A})
|
|
3054
3558
|
* but do not want changes to that specific data to trigger a re-execute of the scope.
|
|
3055
3559
|
*
|
|
3056
|
-
* Note: You may also use {@link unproxy} to get to the raw underlying data structure, which can be used to similar effect.
|
|
3560
|
+
* Note: You may also use {@link Aberdeen.unproxy} to get to the raw underlying data structure, which can be used to similar effect.
|
|
3057
3561
|
*
|
|
3058
3562
|
* @param target - Either a function to execute, or an object (which may also be an Array or a Map) to index.
|
|
3059
3563
|
* @param key - Optional key/index to use when `target` is an object.
|
|
@@ -3073,12 +3577,12 @@ export function unmountAll() {
|
|
|
3073
3577
|
*
|
|
3074
3578
|
*/
|
|
3075
3579
|
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3580
|
+
function peek<T extends object, K extends keyof T>(target: T, key: K): T[K];
|
|
3581
|
+
function peek<K,V>(target: Map<K,V>, key: K): V | undefined;
|
|
3582
|
+
function peek<T>(target: T[], key: number): T | undefined;
|
|
3583
|
+
function peek<T>(target: () => T): T;
|
|
3080
3584
|
|
|
3081
|
-
|
|
3585
|
+
function peek(target: any, key?: any) {
|
|
3082
3586
|
peeking++;
|
|
3083
3587
|
try {
|
|
3084
3588
|
if (arguments.length === 1) {
|
|
@@ -3092,17 +3596,17 @@ export function peek(target: any, key?: any) {
|
|
|
3092
3596
|
}
|
|
3093
3597
|
|
|
3094
3598
|
/** When using a Map as `source`. */
|
|
3095
|
-
|
|
3599
|
+
function map<K, IN, OUT>(
|
|
3096
3600
|
source: Map<K, IN>,
|
|
3097
3601
|
func: (value: IN, key: K) => undefined | OUT,
|
|
3098
3602
|
): Map<K, OUT>;
|
|
3099
3603
|
/** When using an array as `source`. */
|
|
3100
|
-
|
|
3604
|
+
function map<IN, OUT>(
|
|
3101
3605
|
source: Array<IN>,
|
|
3102
3606
|
func: (value: IN, index: number) => undefined | OUT,
|
|
3103
3607
|
): Array<OUT>;
|
|
3104
3608
|
/** When using an object as `source`. */
|
|
3105
|
-
|
|
3609
|
+
function map<IN, const IN_KEY extends string | number | symbol, OUT>(
|
|
3106
3610
|
source: Record<IN_KEY, IN>,
|
|
3107
3611
|
func: (value: IN, index: KeyToString<IN_KEY>) => undefined | OUT,
|
|
3108
3612
|
): Record<string | symbol, OUT>;
|
|
@@ -3149,7 +3653,7 @@ export function map<IN, const IN_KEY extends string | number | symbol, OUT>(
|
|
|
3149
3653
|
* // $activeUserNames becomes proxy({ u1: 'Alice', u2: 'Bob', u3: 'Charlie' })
|
|
3150
3654
|
* ```
|
|
3151
3655
|
*/
|
|
3152
|
-
|
|
3656
|
+
function map(
|
|
3153
3657
|
source: any,
|
|
3154
3658
|
func: (value: any, key: any) => any,
|
|
3155
3659
|
): any {
|
|
@@ -3182,18 +3686,18 @@ export function map(
|
|
|
3182
3686
|
}
|
|
3183
3687
|
|
|
3184
3688
|
/** When using an array as `source`. */
|
|
3185
|
-
|
|
3689
|
+
function multiMap<IN, OUT extends { [key: string | symbol]: any }>(
|
|
3186
3690
|
source: Array<IN>,
|
|
3187
3691
|
func: (value: IN, index: number) => OUT | undefined,
|
|
3188
3692
|
): OUT;
|
|
3189
3693
|
/** When using an object as `source`. */
|
|
3190
|
-
|
|
3694
|
+
function multiMap<
|
|
3191
3695
|
K extends string | number | symbol,
|
|
3192
3696
|
IN,
|
|
3193
3697
|
OUT extends { [key: string | symbol]: any },
|
|
3194
3698
|
>(source: Record<K, IN>, func: (value: IN, index: KeyToString<K>) => OUT | undefined): OUT;
|
|
3195
3699
|
/** When using a Map as `source`. */
|
|
3196
|
-
|
|
3700
|
+
function multiMap<
|
|
3197
3701
|
K,
|
|
3198
3702
|
IN,
|
|
3199
3703
|
OUT extends { [key: string | symbol]: any },
|
|
@@ -3239,7 +3743,7 @@ export function multiMap<
|
|
|
3239
3743
|
* // $itemsById becomes proxy({ a: 10, aa: 100, b: 20, bb: 200, c: 30, cc: 300 })
|
|
3240
3744
|
* ```
|
|
3241
3745
|
*/
|
|
3242
|
-
|
|
3746
|
+
function multiMap(
|
|
3243
3747
|
source: any,
|
|
3244
3748
|
func: (value: any, key: any) => Record<string | symbol, any>,
|
|
3245
3749
|
): any {
|
|
@@ -3257,12 +3761,12 @@ export function multiMap(
|
|
|
3257
3761
|
}
|
|
3258
3762
|
|
|
3259
3763
|
/** When using an object as `array`. */
|
|
3260
|
-
|
|
3764
|
+
function partition<OUT_K extends string | number | symbol, IN_V>(
|
|
3261
3765
|
source: IN_V[],
|
|
3262
3766
|
func: (value: IN_V, key: number) => undefined | OUT_K | OUT_K[],
|
|
3263
3767
|
): Record<OUT_K, Record<number, IN_V>>;
|
|
3264
3768
|
/** When using an object as `source`. */
|
|
3265
|
-
|
|
3769
|
+
function partition<
|
|
3266
3770
|
IN_K extends string | number | symbol,
|
|
3267
3771
|
OUT_K extends string | number | symbol,
|
|
3268
3772
|
IN_V,
|
|
@@ -3271,7 +3775,7 @@ export function partition<
|
|
|
3271
3775
|
func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
|
|
3272
3776
|
): Record<OUT_K, Record<IN_K, IN_V>>;
|
|
3273
3777
|
/** When using a Map as `source`. */
|
|
3274
|
-
|
|
3778
|
+
function partition<
|
|
3275
3779
|
IN_K extends string | number | symbol,
|
|
3276
3780
|
OUT_K extends string | number | symbol,
|
|
3277
3781
|
IN_V,
|
|
@@ -3285,7 +3789,7 @@ export function partition<
|
|
|
3285
3789
|
* Reactively partitions items from a source proxy (array or object) into multiple "bucket" proxies
|
|
3286
3790
|
* based on keys determined by a classifier function.
|
|
3287
3791
|
*
|
|
3288
|
-
* This function iterates through the `source` proxy using {@link onEach}. For each item,
|
|
3792
|
+
* This function iterates through the `source` proxy using {@link Aberdeen.onEach}. For each item,
|
|
3289
3793
|
* it calls the classifier `func`, which should return:
|
|
3290
3794
|
* - A single key (`OUT_K`): The item belongs to the bucket with this key.
|
|
3291
3795
|
* - An array of keys (`OUT_K[]`): The item belongs to all buckets specified in the array.
|
|
@@ -3300,7 +3804,7 @@ export function partition<
|
|
|
3300
3804
|
* or changes in dependencies read by the `func` will cause the output partitioning to update automatically.
|
|
3301
3805
|
* Buckets are created dynamically as needed and removed when they become empty.
|
|
3302
3806
|
*
|
|
3303
|
-
* @param source - The input proxied Array or Record (e.g., created by {@link proxy}) containing the items to partition.
|
|
3807
|
+
* @param source - The input proxied Array or Record (e.g., created by {@link Aberdeen.proxy}) containing the items to partition.
|
|
3304
3808
|
* @param func - A classifier function `(value: IN_V, key: IN_K | number) => undefined | OUT_K | OUT_K[]`.
|
|
3305
3809
|
* It receives the item's value and its original key/index from the `source`. It returns the bucket key(s)
|
|
3306
3810
|
* the item belongs to, or `undefined` to ignore the item.
|
|
@@ -3347,7 +3851,7 @@ export function partition<
|
|
|
3347
3851
|
* console.log($usersByTag);
|
|
3348
3852
|
* ```
|
|
3349
3853
|
*/
|
|
3350
|
-
|
|
3854
|
+
function partition<
|
|
3351
3855
|
IN_K extends string | number | symbol,
|
|
3352
3856
|
OUT_K extends string | number | symbol,
|
|
3353
3857
|
IN_V,
|
|
@@ -3378,34 +3882,7 @@ export function partition<
|
|
|
3378
3882
|
return out;
|
|
3379
3883
|
}
|
|
3380
3884
|
|
|
3381
|
-
|
|
3382
|
-
* Renders a live, recursive dump of a proxied data structure (or any value)
|
|
3383
|
-
* into the DOM at the current {@link A | A} insertion point.
|
|
3384
|
-
*
|
|
3385
|
-
* Uses `<ul>` and `<li>` elements to display object properties and array items.
|
|
3386
|
-
* Updates reactively if the dumped data changes. Primarily intended for debugging purposes.
|
|
3387
|
-
*
|
|
3388
|
-
* @param data - The proxied data structure (or any value) to display.
|
|
3389
|
-
* @returns The original `data` argument, allowing for chaining.
|
|
3390
|
-
* @template T - The type of the data being dumped.
|
|
3391
|
-
*
|
|
3392
|
-
* @example Dumping reactive state
|
|
3393
|
-
* ```typescript
|
|
3394
|
-
* import A from 'aberdeen';
|
|
3395
|
-
*
|
|
3396
|
-
* const $state = A.proxy({
|
|
3397
|
-
* user: { name: 'Frank', kids: 1 },
|
|
3398
|
-
* items: ['a', 'b']
|
|
3399
|
-
* });
|
|
3400
|
-
*
|
|
3401
|
-
* A('h2#Live State Dump');
|
|
3402
|
-
* A.dump($state);
|
|
3403
|
-
*
|
|
3404
|
-
* // Change state later, the dump in the DOM will update
|
|
3405
|
-
* setTimeout(() => { $state.user.kids++; $state.items.push('c'); }, 2000);
|
|
3406
|
-
* ```
|
|
3407
|
-
*/
|
|
3408
|
-
export function dump<T>(data: T) {
|
|
3885
|
+
function dump<T>(data: T) {
|
|
3409
3886
|
const org = dumpSeen;
|
|
3410
3887
|
dumpSeen ||= new Set();
|
|
3411
3888
|
try {
|
|
@@ -3449,20 +3926,7 @@ function rawDump<T>(data: T, seen: Set<any>) {
|
|
|
3449
3926
|
}
|
|
3450
3927
|
}
|
|
3451
3928
|
|
|
3452
|
-
|
|
3453
|
-
* When set on an object or its prototype chain, {@link dump} calls this as a render function
|
|
3454
|
-
* (with the object as `this`) instead of its default recursive rendering. If the value is not
|
|
3455
|
-
* a function, it's treated as a string to display.
|
|
3456
|
-
*
|
|
3457
|
-
* @example
|
|
3458
|
-
* ```typescript
|
|
3459
|
-
* class Color {
|
|
3460
|
-
* constructor(public r: number, public g: number, public b: number) {}
|
|
3461
|
-
* [CUSTOM_DUMP]() { A(`#rgb(${this.r}, ${this.g}, ${this.b})`); }
|
|
3462
|
-
* }
|
|
3463
|
-
* ```
|
|
3464
|
-
*/
|
|
3465
|
-
export const CUSTOM_DUMP = Symbol("CUSTOM_DUMP");
|
|
3929
|
+
const CUSTOM_DUMP = Symbol("CUSTOM_DUMP");
|
|
3466
3930
|
|
|
3467
3931
|
(Date.prototype as any)[CUSTOM_DUMP] = function(this: Date) {
|
|
3468
3932
|
A("#<Date> " + this.toISOString());
|
|
@@ -3495,7 +3959,7 @@ function handleError(e: any, showMessage: boolean) {
|
|
|
3495
3959
|
}
|
|
3496
3960
|
|
|
3497
3961
|
/** @internal */
|
|
3498
|
-
|
|
3962
|
+
function withEmitHandler(
|
|
3499
3963
|
handler: (
|
|
3500
3964
|
target: TargetType,
|
|
3501
3965
|
index: any,
|
|
@@ -3514,8 +3978,25 @@ export function withEmitHandler(
|
|
|
3514
3978
|
}
|
|
3515
3979
|
|
|
3516
3980
|
|
|
3517
|
-
|
|
3518
|
-
|
|
3981
|
+
/**
|
|
3982
|
+
* Build the `:root` CSS variable declarations from {@link Aberdeen.cssVars}, without subscribing.
|
|
3983
|
+
* Shared between the reactive `<head>` injection (browser) and {@link buildHeadCss} (SSR).
|
|
3984
|
+
* @internal
|
|
3985
|
+
*/
|
|
3986
|
+
function buildCssVars(): string {
|
|
3987
|
+
if (isEmpty(cssVars)) return "";
|
|
3988
|
+
let css = ":root{";
|
|
3989
|
+
for (const [key, value] of Object.entries(cssVars)) {
|
|
3990
|
+
const varName = DIGIT_FIRST.test(String(key)) ? `m${key}` : key;
|
|
3991
|
+
css += `--${varName}:${value};`;
|
|
3992
|
+
}
|
|
3993
|
+
return css + "}\n";
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
// Automatically add cssVars and cssSnippets (from insertCss) into a <head> <style> tag. This
|
|
3997
|
+
// runs for any instance that has a document — including a server instance, whose <head> the
|
|
3998
|
+
// SSR entry serializes after rendering.
|
|
3999
|
+
if (document) {
|
|
3519
4000
|
leakScope(() => {
|
|
3520
4001
|
A(() => {
|
|
3521
4002
|
if (isEmpty(cssSnippets) && isEmpty(cssVars)) return;
|
|
@@ -3525,14 +4006,8 @@ if (typeof document !== "undefined") {
|
|
|
3525
4006
|
A('#', value);
|
|
3526
4007
|
});
|
|
3527
4008
|
A(() => {
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
for(const [key, value] of Object.entries(cssVars)) {
|
|
3531
|
-
const varName = DIGIT_FIRST.test(String(key)) ? `m${key}` : key;
|
|
3532
|
-
css += `--${varName}:${value};`;
|
|
3533
|
-
}
|
|
3534
|
-
css += "}\n";
|
|
3535
|
-
A('#', css);
|
|
4009
|
+
let css = buildCssVars();
|
|
4010
|
+
if (css) A('#', css);
|
|
3536
4011
|
})
|
|
3537
4012
|
});
|
|
3538
4013
|
});
|
|
@@ -3540,6 +4015,10 @@ if (typeof document !== "undefined") {
|
|
|
3540
4015
|
});
|
|
3541
4016
|
}
|
|
3542
4017
|
|
|
4018
|
+
// Honor the per-instance options.
|
|
4019
|
+
// Make `create`/`destroy` transitions no-ops for this instance when disabled (e.g. for SSR).
|
|
4020
|
+
if (options.transitions === false) SPECIAL_PROPS.create = SPECIAL_PROPS.destroy = () => {};
|
|
4021
|
+
|
|
3543
4022
|
/**
|
|
3544
4023
|
* The main Aberdeen API. `A` is itself a callable function for building reactive DOM trees
|
|
3545
4024
|
* (creating elements, setting attributes, adding content). All other Aberdeen functions and
|
|
@@ -3556,35 +4035,42 @@ if (typeof document !== "undefined") {
|
|
|
3556
4035
|
* });
|
|
3557
4036
|
* ```
|
|
3558
4037
|
*/
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
/**
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
/**
|
|
3576
|
-
|
|
3577
|
-
/**
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
4038
|
+
return Object.assign(A, {
|
|
4039
|
+
clean,
|
|
4040
|
+
clone,
|
|
4041
|
+
copy,
|
|
4042
|
+
count,
|
|
4043
|
+
cssVars,
|
|
4044
|
+
CUSTOM_DUMP,
|
|
4045
|
+
darkMode,
|
|
4046
|
+
defaultEmitHandler,
|
|
4047
|
+
derive,
|
|
4048
|
+
dump,
|
|
4049
|
+
/** EMPTY sentinel (used by `aberdeen/prediction`). @internal */ EMPTY,
|
|
4050
|
+
insertCss,
|
|
4051
|
+
insertGlobalCss,
|
|
4052
|
+
invertString,
|
|
4053
|
+
isEmpty,
|
|
4054
|
+
/** Internal scope helper (used by `aberdeen/route`). @internal */ leakScope,
|
|
4055
|
+
map,
|
|
4056
|
+
/** Map/Set size tracking symbol (used by `aberdeen/prediction`). @internal */ MAP_SIZE_SYMBOL,
|
|
4057
|
+
merge,
|
|
4058
|
+
mount,
|
|
4059
|
+
multiMap,
|
|
4060
|
+
OPAQUE,
|
|
4061
|
+
NO_COPY,
|
|
4062
|
+
onEach,
|
|
4063
|
+
partition,
|
|
4064
|
+
peek,
|
|
4065
|
+
proxy,
|
|
4066
|
+
ref,
|
|
4067
|
+
runQueue,
|
|
4068
|
+
setErrorHandler,
|
|
4069
|
+
setSpacingCssVars,
|
|
4070
|
+
unmountAll,
|
|
4071
|
+
unproxy,
|
|
4072
|
+
/** The window this instance renders against (used by `aberdeen/route`). @internal */ window,
|
|
4073
|
+
/** Run a function with a custom emit handler (used by `aberdeen/prediction`). @internal */ withEmitHandler,
|
|
4074
|
+
}) as unknown as Aberdeen;
|
|
4075
|
+
|
|
4076
|
+
}
|