aberdeen 0.4.0 → 1.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.
@@ -1,599 +1,723 @@
1
1
  /**
2
- * Normally, changes to `Store`s are reacted to asynchronously, in an (optimized)
3
- * batch, after a timeout of 0s. Calling `runQueue()` will do so immediately
4
- * and synchronously. Doing so may be helpful in cases where you need some DOM
5
- * modification to be done synchronously.
2
+ * Forces the immediate and synchronous execution of all pending reactive updates.
6
3
  *
7
- * This function is re-entrant, meaning it is safe to call `runQueue` from a
8
- * function that is called due to another (automatic) invocation of `runQueue`.
4
+ * Normally, changes to observed data sources (like proxied objects or arrays)
5
+ * are processed asynchronously in a batch after a brief timeout (0ms). This function
6
+ * allows you to bypass the timeout and process the update queue immediately.
7
+ *
8
+ * This can be useful in specific scenarios where you need the DOM to be updated
9
+ * synchronously.
10
+ *
11
+ * This function is re-entrant, meaning it is safe to call `runQueue` from within
12
+ * a function that is itself being executed as part of an update cycle triggered
13
+ * by a previous (or the same) `runQueue` call.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const data = proxy("before");
18
+ *
19
+ * $({text: data});
20
+ * console.log(1, document.body.innerHTML); // before
21
+ *
22
+ * // Make an update that should cause the DOM to change.
23
+ * data.value = "after";
24
+ *
25
+ * // Normally, the DOM update would happen after a timeout.
26
+ * // But this causes an immediate update:
27
+ * runQueue();
28
+ *
29
+ * console.log(2, document.body.innerHTML); // after
30
+ * ```
9
31
  */
10
32
  export declare function runQueue(): void;
11
33
  /**
12
- * A promise-like object that you can `await`. It will resolve *after* the current batch
13
- * of DOM-write operations has completed. This is the best time to retrieve DOM properties
14
- * that dependent on a layout being completed, such as `offsetHeight`.
34
+ * A sort key, as used by {@link onEach}, is a value that determines the order of items. It can
35
+ * be a number, string, or an array of numbers/strings. The sort key is used to sort items
36
+ * based on their values. The sort key can also be `undefined`, which indicates that the item
37
+ * should be ignored.
38
+ * @private
39
+ */
40
+ export type SortKeyType = number | string | Array<number | string> | undefined;
41
+ /**
42
+ * Creates a new string that has the opposite sort order compared to the input string.
15
43
  *
16
- * By batching DOM reads separately from DOM writes, this prevents the browser from
17
- * interleaving layout reads and writes, which can force additional layout recalculations.
18
- * This helps reduce visual glitches and flashes by ensuring the browser doesn't render
19
- * intermediate DOM states during updates.
44
+ * This is achieved by flipping the bits of each character code in the input string.
45
+ * The resulting string is intended for use as a sort key, particularly with the
46
+ * `makeKey` function in {@link onEach}, to achieve a descending sort order.
20
47
  *
21
- * Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM read
22
- * operations happen before any DOM writes in the same queue cycle, minimizing layout thrashing.
48
+ * **Warning:** The output string will likely contain non-printable characters or
49
+ * appear as gibberish and should not be displayed to the user.
23
50
  *
24
- * See `transitions.js` for some examples.
51
+ * @example
52
+ * ```typescript
53
+ * const users = proxy([
54
+ * { id: 1, name: 'Charlie', score: 95 },
55
+ * { id: 2, name: 'Alice', score: 100 },
56
+ * { id: 3, name: 'Bob', score: 90 },
57
+ * ]);
58
+ *
59
+ * onEach(users, (user) => {
60
+ * $(`p:${user.name}: ${user.score}`);
61
+ * }, (user) => invertString(user.name)); // Reverse alphabetic order
62
+ * ```
63
+ *
64
+ * @param input The string whose sort order needs to be inverted.
65
+ * @returns A new string that will sort in the reverse order of the input string.
66
+ * @see {@link onEach} for usage with sorting.
25
67
  */
26
- export declare const DOM_READ_PHASE: {
27
- then: (fulfilled: () => void) => any;
28
- };
68
+ export declare function invertString(input: string): string;
69
+ export declare function onEach<T>(target: Array<undefined | T>, render: (value: T, index: number) => void, makeKey?: (value: T, key: any) => SortKeyType): void;
70
+ export declare function onEach<K extends string | number | symbol, T>(target: Record<K, undefined | T>, render: (value: T, index: K) => void, makeKey?: (value: T, key: K) => SortKeyType): void;
29
71
  /**
30
- * A promise-like object that you can `await`. It will resolve *after* the current
31
- * DOM_READ_PHASE has completed (if any) and after any DOM triggered by Aberdeen
32
- * have completed. This is a good time to do little manual DOM tweaks that depend
33
- * on a *read phase* first, like triggering transitions.
72
+ * Reactively checks if an observable array or object is empty.
73
+ *
74
+ * This function not only returns the current emptiness state but also establishes
75
+ * a reactive dependency. If the emptiness state of the `proxied` object or array
76
+ * changes later (e.g., an item is added to an empty array, or the last property
77
+ * is deleted from an object), the scope that called `isEmpty` will be automatically
78
+ * scheduled for re-evaluation.
34
79
  *
35
- * By batching DOM writes separately from DOM reads, this prevents the browser from
36
- * interleaving layout reads and writes, which can force additional layout recalculations.
37
- * This helps reduce visual glitches and flashes by ensuring the browser doesn't render
38
- * intermediate DOM states during updates.
80
+ * @param proxied The observable array or object (obtained via `observe()`) to check.
81
+ * @returns `true` if the array has length 0 or the object has no own enumerable properties, `false` otherwise.
39
82
  *
40
- * Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM write
41
- * operations happen after all DOM reads in the same queue cycle, minimizing layout thrashing.
83
+ * @example
84
+ * ```typescript
85
+ * const items = proxy([]);
42
86
  *
43
- * See `transitions.js` for some examples.
87
+ * // Reactively display a message if the items array is empty
88
+ * $('div', () => {
89
+ * if (isEmpty(items)) {
90
+ * $('p', 'i:No items yet!');
91
+ * } else {
92
+ * onEach(items, item=>$('p:'+item));
93
+ * }
94
+ * });
95
+ *
96
+ * // Adding an item will automatically remove the "No items yet!" message
97
+ * setInterval(() => {
98
+ * if (!items.length || Math.random()>0.5) items.push('Item');
99
+ * else items.length = 0;
100
+ * }, 1000)
101
+ * ```
44
102
  */
45
- export declare const DOM_WRITE_PHASE: {
46
- then: (fulfilled: () => void) => any;
47
- };
48
- export interface Store {
49
- /**
50
- * Return a `Store` deeper within the tree by resolving the given `path`,
51
- * subscribing to every level.
52
- * In case `undefined` is encountered while resolving the path, a newly
53
- * created `Store` containing `undefined` is returned. In that case, the
54
- * `Store`'s [[`isDetached`]] method will return `true`.
55
- * In case something other than a collection is encountered, an error is thrown.
56
- */
57
- (...path: any[]): Store;
58
- }
59
- export declare class Store {
60
- /**
61
- * Create a new `Store` with `undefined` as its initial value.
62
- */
63
- constructor();
64
- /**
65
- * Create a new `Store`.
66
- * @param value The initial value. Plain objects, arrays and `Map`s, are converted
67
- * into a tree of nested `Store`s. When another `Store` is included somewhere in that
68
- * input tree, a reference is made.
69
- */
70
- constructor(value: any);
71
- /**
72
- * @returns The index for this Store within its parent collection. This will be a `number`
73
- * when the parent collection is an array, a `string` when it's an object, or any data type
74
- * when it's a `Map`.
75
- *
76
- * @example
77
- * ```
78
- * let store = new Store({x: 123})
79
- * let subStore = store.ref('x')
80
- * subStore.get() // 123
81
- * subStore.index() // 'x'
82
- * ```
83
- */
84
- index(): any;
85
- /**
86
- * Retrieve the value for store, subscribing the observe scope to changes.
87
- *
88
- * @param depth Limit the depth of the retrieved data structure to this positive integer.
89
- * When `depth` is `1`, only a single level of the value at `path` is unpacked. This
90
- * makes no difference for primitive values (like strings), but for objects, maps and
91
- * arrays, it means that each *value* in the resulting data structure will be a
92
- * reference to the `Store` for that value.
93
- *
94
- * @returns The resulting value (or `undefined` if the `Store` does not exist).
95
- */
96
- get(depth?: number): any;
97
- /**
98
- * Exactly like {@link Store.get}, except that when executed from an observe scope,
99
- * we will not subscribe to changes in the data retrieved data.
100
- */
101
- peek(depth?: number): any;
102
- /**
103
- * Like {@link Store.get}, but with return type checking.
104
- *
105
- * @param expectType A string specifying what type the.get is expected to return. Options are:
106
- * "undefined", "null", "boolean", "number", "string", "function", "array", "map"
107
- * and "object". If the store holds a different type of value, a `TypeError`
108
- * exception is thrown.
109
- * @returns
110
- */
111
- getTyped(expectType: String, depth?: number): any;
112
- /**
113
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `number`.
114
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
115
- */
116
- getNumber(): number;
117
- /**
118
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `string`.
119
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
120
- */
121
- getString(): string;
122
- /**
123
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `boolean`.
124
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
125
- */
126
- getBoolean(): boolean;
127
- /**
128
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `function`.
129
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
130
- */
131
- getFunction(): (Function);
132
- /**
133
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `array`.
134
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
135
- */
136
- getArray(depth?: number): any[];
137
- /**
138
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `object`.
139
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
140
- */
141
- getObject(depth?: number): object;
142
- /**
143
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `map`.
144
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
145
- */
146
- getMap(depth?: number): Map<any, any>;
147
- /**
148
- * Like {@link Store.get}, but with a default value (returned when the Store
149
- * contains `undefined`). This default value is also used to determine the expected type,
150
- * and to throw otherwise.
151
- *
152
- * @example
153
- * ```
154
- * let store = new Store({x: 42})
155
- * store('x').getOr(99) // 42
156
- * store('y').getOr(99) // 99
157
- * store('x').getOr('hello') // throws TypeError (because 42 is not a string)
158
- * ```
159
- */
160
- getOr<T>(defaultValue: T): T;
161
- /**
162
- * Checks if the collection held in `Store` is empty, and subscribes the current scope to changes of the emptiness of this collection.
163
- *
164
- * @returns When the collection is not empty `true` is returned. If it is empty or if the value is undefined, `false` is returned.
165
- * @throws When the value is not a collection and not undefined, an Error will be thrown.
166
- */
167
- isEmpty(): boolean;
168
- /**
169
- * Returns the number of items in the collection held in Store, and subscribes the current scope to changes in this count.
170
- *
171
- * @returns The number of items contained in the collection, or 0 if the value is undefined.
172
- * @throws When the value is not a collection and not undefined, an Error will be thrown.
173
- */
174
- count(): number;
175
- /**
176
- * Returns a strings describing the type of the `Store` value, subscribing to changes of this type.
177
- * Note: this currently also subscribes to changes of primitive values, so changing a value from 3 to 4
178
- * would cause the scope to be rerun. This is not great, and may change in the future. This caveat does
179
- * not apply to changes made *inside* an object, `Array` or `Map`.
180
- *
181
- * @returns Possible options: "undefined", "null", "boolean", "number", "string", "function", "array", "map" or "object".
182
- */
183
- getType(): string;
184
- /**
185
- * Returns a new `Store` that will always hold either the value of `whenTrue` or the value
186
- * of `whenFalse` depending on whether the original `Store` is truthy or not.
187
- *
188
- * @param whenTrue The value set to the return-`Store` while `this` is truthy. This can be
189
- * any type of value. If it's a `Store`, the return-`Store` will reference the same
190
- * data (so *no* deep copy will be made).
191
- * @param whenFalse Like `whenTrue`, but for falsy values (false, undefined, null, 0, "").
192
- * @returns A store holding the result value. The value will keep getting updated while
193
- * the observe context from which `if()` was called remains active.
194
- */
195
- if(whenTrue: any[], whenFalse?: any[]): Store;
196
- /**
197
- * Sets the `Store` value to the given argument.
198
- *
199
- * When a `Store` is passed in as the value, its value will be copied (subscribing to changes). In
200
- * case the value is an object, an `Array` or a `Map`, a *reference* to that data structure will
201
- * be created, so that changes made through one `Store` will be reflected through the other. Be
202
- * carefull not to create loops in your `Store` tree that way, as that would cause any future
203
- * call to {@link Store.get} to throw a `RangeError` (Maximum call stack size exceeded.)
204
- *
205
- * If you intent to make a copy instead of a reference, call {@link Store.get} on the origin `Store`.
206
- *
207
- * @returns The `Store` itself, for chaining other methods.
208
- *
209
- * @example
210
- * ```
211
- * let store = new Store() // Value is `undefined`
212
- *
213
- * store.set(6)
214
- * store.get() // 6
215
- *
216
- * store.set({}) // Change value to an empty object
217
- * store('a', 'b', 'c').set('d') // Create parent path as objects
218
- * store.get() // {x: 6, a: {b: {c: 'd'}}}
219
- *
220
- * store.set(42) // Overwrites all of the above
221
- * store.get() // 42
222
- *
223
- * store('x').set(6) // Throw Error (42 is not a collection)
224
- * ```
225
- */
226
- set(newValue: any): Store;
227
- /**
228
- * Sets the `Store` to the given `mergeValue`, but without deleting any pre-existing
229
- * items when a collection overwrites a similarly typed collection. This results in
230
- * a deep merge.
231
- *
232
- * @returns The `Store` itself, for chaining other methods.
233
- *
234
- * @example
235
- * ```
236
- * let store = new Store({a: {x: 1}})
237
- * store.merge({a: {y: 2}, b: 3})
238
- * store.get() // {a: {x: 1, y: 2}, b: 3}
239
- * ```
240
- */
241
- merge(mergeValue: any): Store;
242
- /**
243
- * Sets the value for the store to `undefined`, which causes it to be omitted from the map (or array, if it's at the end)
244
- *
245
- * @returns The `Store` itself, for chaining other methods.
246
- *
247
- * @example
248
- * ```
249
- * let store = new Store({a: 1, b: 2})
250
- * store('a').delete()
251
- * store.get() // {b: 2}
252
- *
253
- * store = new Store(['a','b','c'])
254
- * store(1).delete()
255
- * store.get() // ['a', undefined, 'c']
256
- * store(2).delete()
257
- * store.get() // ['a']
258
- * store.delete()
259
- * store.get() // undefined
260
- * ```
261
- */
262
- delete(): Store;
263
- /**
264
- * Pushes a value to the end of the Array that is at the specified path in the store.
265
- * If that store path is `undefined`, an Array is created first.
266
- * The last argument is the value to be added, any earlier arguments indicate the path.
267
- *
268
- * @returns The index at which the item was appended.
269
- * @throws TypeError when the store contains a primitive data type.
270
- *
271
- * @example
272
- * ```
273
- * let store = new Store()
274
- * store.push(3) // Creates the array
275
- * store.push(6)
276
- * store.get() // [3,6]
277
- *
278
- * store = new Store({myArray: [1,2]})
279
- * store('myArray').push(3)
280
- * store.get() // {myArray: [1,2,3]}
281
- * ```
282
- */
283
- push(newValue: any): number;
284
- /**
285
- * {@link Store.peek} the current value, pass it through `func`, and {@link Store.set} the resulting
286
- * value.
287
- * @param func The function transforming the value.
288
- * @returns The `Store` itself, for chaining other methods.
289
- */
290
- modify(func: (value: any) => any): Store;
291
- /**
292
- * Iterate the specified collection (Array, Map or object), running the given code block for each item.
293
- * When items are added to the collection at some later point, the code block will be ran for them as well.
294
- * When an item is removed, the {@link Store.clean} handlers left by its code block are executed.
295
- *
296
- * @param renderer The function to be called for each item. It receives the item's `Store` object as its only argument.
297
- * @param makeSortKey An optional function that, given an items `Store` object, returns a value to be sorted on.
298
- * This value can be a number, a string, or an array containing a combination of both. When undefined is returned,
299
- * the item is *not* rendered. If `makeSortKey` is not specified, the output will be sorted by `index()`.
300
- */
301
- onEach(renderer: (store: Store) => void, makeSortKey?: (store: Store) => any): void;
302
- /**
303
- * Derive a new `Store` from this `Store`, by reactively passing its value
304
- * through the specified function.
305
- * @param func Your function. It should accept a the input store's value, and return
306
- * a result to be reactively set to the output store.
307
- * @returns The output `Store`.
308
- * @example
309
- * ```javascript
310
- * const store = new Store(21)
311
- * const double = store.derive(v => v*2)
312
- * double.get() // 42
313
- *
314
- * store.set(100)
315
- * runQueue() // Or after a setTimeout 0, due to batching
316
- * double.get() // 200
317
- * ```
318
- */
319
- derive(func: (value: any) => any): Store;
320
- /**
321
- * Applies a filter/map function on each item within the `Store`'s collection,
322
- * and reactively manages the returned `Map` `Store` to hold any results.
323
- *
324
- * @param func - Function that transform the given store into an output value or
325
- * `undefined` in case this value should be skipped:
326
- *
327
- * @returns - A array/map/object `Store` with the values returned by `func` and the
328
- * corresponding keys from the original map, array or object `Store`.
329
- *
330
- * When items disappear from the `Store` or are changed in a way that `func` depends
331
- * upon, the resulting items are removed from the output `Store` as well. When multiple
332
- * input items produce the same output keys, this may lead to unexpected results.
333
- */
334
- map(func: (store: Store) => any): Store;
335
- /**
336
- * Applies a filter/map function on each item within the `Store`'s collection,
337
- * each of which can deliver any number of key/value pairs, and reactively manages the
338
- * returned map `Store` to hold any results.
339
- *
340
- * @param func - Function that transform the given store into output values
341
- * that can take one of the following forms:
342
- * - an `Object` or a `Map`: Each key/value pair will be added to the output `Store`.
343
- * - anything else: No key/value pairs are added to the output `Store`.
344
- *
345
- * @returns - A map `Store` with the key/value pairs returned by all `func` invocations.
346
- *
347
- * When items disappear from the `Store` or are changed in a way that `func` depends
348
- * upon, the resulting items are removed from the output `Store` as well. When multiple
349
- * input items produce the same output keys, this may lead to unexpected results.
350
- */
351
- multiMap(func: (store: Store) => any): Store;
352
- /**
353
- * Dump a live view of the `Store` tree as HTML text, `ul` and `li` nodes at
354
- * the current mount position. Meant for debugging purposes.
355
- * @returns The `Store` itself, for chaining other methods.
356
- */
357
- dump(): Store;
103
+ export declare function isEmpty(proxied: TargetType): boolean;
104
+ /** @private */
105
+ export interface ValueRef<T> {
106
+ value: T;
358
107
  }
359
108
  /**
360
- * Modifies the *parent* DOM element in the current reactive scope, or adds
361
- * new DOM elements to it.
362
- *
363
- * @param args - Arguments that define how to modify/create elements.
364
- *
365
- * ### String arguments
366
- * Create new elements with optional classes and text content:
367
- * ```js
368
- * $('div.myClass') // <div class="myClass"></div>
369
- * $('span.c1.c2:Hello') // <span class="c1 c2">Hello</span>
370
- * $('p:Some text') // <p>Some text</p>
371
- * $('.my-thing') // <div class="my-thing"></div>
372
- * $('div', 'span', 'p.cls') // <div><span<p class="cls"></p></span></div>
373
- * $(':Just some text!') // Just some text! (No new element, just a text node)
109
+ * Reactively counts the number of properties in an objects.
110
+ *
111
+ * @param proxied The observable object to count. In case an `array` is passed in, a {@link ref} to its `.length` will be returned.
112
+ * @returns an observable object for which the `value` property reflects the number of properties in `proxied` with a value other than `undefined`.
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * const items = proxy({x: 3, y: 7} as any);
117
+ * const cnt = count(items);
118
+ *
119
+ * // Create a DOM text node for the count:
120
+ * $('div', {text: cnt});
121
+ * // <div>2</div>
122
+
123
+ * // Or we can use it in an {@link observe} function:
124
+ * observe(() => console.log("The count is now", cnt.value));
125
+ * // The count is now 2
126
+ *
127
+ * // Adding/removing items will update the count
128
+ * items.z = 12;
129
+ * // Asynchronously, after 0ms:
130
+ * // <div>3</div>
131
+ * // The count is now 3
374
132
  * ```
133
+ */
134
+ export declare function count(proxied: TargetType): ValueRef<number>;
135
+ export declare function proxy<T extends DatumType>(target: Array<T>): Array<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T>;
136
+ export declare function proxy<T extends object>(target: T): T;
137
+ export declare function proxy<T extends DatumType>(target: T): ValueRef<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T>;
138
+ /**
139
+ * Returns the original, underlying data target from a reactive proxy created by {@link proxy}.
140
+ * If the input `target` is not a proxy, it is returned directly.
141
+ *
142
+ * This is useful when you want to avoid triggering subscriptions during read operations or
143
+ * re-executes during write operations. Using {@link peek} is an alternative way to achieve this.
375
144
  *
376
- * ### Object arguments
377
- * Set properties, attributes, events and special features:
378
- * ```js
379
- * // Classes (dot prefix)
380
- * $('div', {'.active': true}) // Add class
381
- * $('div', {'.hidden': false}) // Remove (or don't add) class
382
- * $('div', {'.selected': myStore}) // Reactively add/remove class
383
- *
384
- * // Styles (dollar prefixed and camel-cased CSS properties)
385
- * $('div', {$color: 'red'}) // style.color = 'red'
386
- * $('div', {$marginTop: '10px'}) // style.marginTop = '10px'
387
- * $('div', {$color: myColorStore}) // Reactively change color
388
- *
389
- * // Events (function values)
390
- * $('button', {click: () => alert()}) // Add click handler
391
- *
392
- * // Properties (boolean values, `selectedIndex`, `value`)
393
- * $('input', {disabled: true}) // el.disabled = true
394
- * $('input', {value: 'test'}) // el.value = 'test'
395
- * $('select', {selectedIndex: 2}) // el.selectedIndex = 2
396
- *
397
- * // Transitions
398
- * $('div', {create: 'fade-in'}) // Add class on create
399
- * $('div', {create: el => {...}}) // Run function on create
400
- * $('div', {destroy: 'fade-out'}) // Add class before remove
401
- * $('div', {destroy: el => {...}}) // Run function before remove
402
- *
403
- * // Content
404
- * $('div', {html: '<b>Bold</b>'}) // Set innerHTML
405
- * $('div', {text: 'Plain text'}) // Add text node
406
- * const myElement = document.createElement('video')
407
- * $('div', {element: myElement}) // Add existing DOM element
408
- *
409
- * // Regular attributes (everything else)
410
- * $('div', {title: 'Info'}) // el.setAttribute('title', 'info')
145
+ * @param target - A proxied object, array, or any other value.
146
+ * @returns The underlying (unproxied) data, or the input value if it wasn't a proxy.
147
+ * @template T - The type of the target.
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * const userProxy = proxy({ name: 'Frank' });
152
+ * const rawUser = unproxy(userProxy);
153
+ *
154
+ * // Log reactively
155
+ * $(() => console.log('proxied', userProxy.name));
156
+ * // The following will only ever log once, as we're not subscribing to any observable
157
+ * $(() => console.log('unproxied', rawUser.name));
158
+ *
159
+ * // This cause the first log to run again:
160
+ * setTimeout(() => userProxy.name += '!', 1000);
161
+ *
162
+ * // This doesn't cause any new logs:
163
+ * setTimeout(() => rawUser.name += '?', 2000);
164
+ *
165
+ * // Both userProxy and rawUser end up as `{name: 'Frank!?'}`
166
+ * setTimeout(() => {
167
+ * console.log('final proxied', userProxy)
168
+ * console.log('final unproxied', rawUser)
169
+ * }, 3000);
170
+ * ```
171
+ */
172
+ export declare function unproxy<T>(target: T): T;
173
+ /**
174
+ * Recursively copies properties or array items from `src` to `dst`.
175
+ * It's designed to work efficiently with reactive proxies created by {@link proxy}.
176
+ *
177
+ * - **Minimizes Updates:** When copying between objects/arrays (proxied or not), if a nested object
178
+ * exists in `dst` with the same constructor as the corresponding object in `src`, `copy`
179
+ * will recursively copy properties into the existing `dst` object instead of replacing it.
180
+ * This minimizes change notifications for reactive updates.
181
+ * - **Handles Proxies:** Can accept proxied or unproxied objects/arrays for both `dst` and `src`.
182
+ *
183
+ * @param dst - The destination object/array (proxied or unproxied).
184
+ * @param src - The source object/array (proxied or unproxied). It won't be modified.
185
+ * @param flags - Bitmask controlling copy behavior:
186
+ * - {@link MERGE}: Performs a partial update. Properties in `dst` not present in `src` are kept.
187
+ * `null`/`undefined` in `src` delete properties in `dst`. Handles partial array updates via object keys.
188
+ * - {@link SHALLOW}: Performs a shallow copy; when an array/object of the right type doesn't exist in `dst` yet, a reference to the array/object in `src` will be made, instead of creating a copy. If the array/object already exists, it won't be replaced (by a reference), but all items will be individually checked and copied like normal, keeping changes (and therefore UI updates) to a minimum.
189
+ * @template T - The type of the objects being copied.
190
+ * @throws Error if attempting to copy an array into a non-array or vice versa (unless {@link MERGE} is set, allowing for sparse array updates).
191
+ *
192
+ * @example Basic Copy
193
+ * ```typescript
194
+ * const source = proxy({ a: 1, b: { c: 2 } });
195
+ * const dest = proxy({ b: { d: 3 } });
196
+ * copy(dest, source);
197
+ * console.log(dest); // proxy({ a: 1, b: { c: 2 } })
411
198
  * ```
412
199
  *
413
- * When a `Store` is passed as a value, a seperate observe-scope will
414
- * be created for it, such that when the `Store` changes, only *that*
415
- * UI property will need to be updated.
416
- * So in the following example, when `colorStore` changes, only the
417
- * `color` CSS property will be updated.
418
- * ```js
419
- * $('div', {
420
- * '.active': activeStore, // Reactive class
421
- * $color: colorStore, // Reactive style
422
- * text: textStore // Reactive text
423
- * })
200
+ * @example MERGE
201
+ * ```typescript
202
+ * const source = { b: { c: 99 }, d: undefined }; // d: undefined will delete
203
+ * const dest = proxy({ a: 1, b: { x: 5 }, d: 4 });
204
+ * copy(dest, source, MERGE);
205
+ * console.log(dest); // proxy({ a: 1, b: { c: 99, x: 5 } })
424
206
  * ```
425
207
  *
426
- * ### Two-way input binding
427
- * Set the initial value of an <input> <textarea> or <select> to that
428
- * of a `Store`, and then start reflecting user changes to the former
429
- * in the latter.
430
- * ```js
431
- * $('input', {bind: myStore}) // Binds input.value
208
+ * @example Partial Array Update with MERGE
209
+ * ```typescript
210
+ * const messages = proxy(['msg1', 'msg2', 'msg3']);
211
+ * const update = { 1: 'updated msg2' }; // Update using object key as index
212
+ * copy(messages, update, MERGE);
213
+ * console.log(messages); // proxy(['msg1', 'updated msg2', 'msg3'])
432
214
  * ```
433
- * This is a special case, as changes to the `Store` will *not* be
434
- * reflected in the UI.
435
215
  *
436
- * ### Function arguments
437
- * Create child scopes that re-run on observed `Store` changes:
438
- * ```js
439
- * $('div', () => {
440
- * $(myStore.get() ? 'span' : 'p') // Reactive element type
441
- * })
216
+ * @example SHALLOW
217
+ * ```typescript
218
+ * const source = { nested: [1, 2] };
219
+ * const dest = {};
220
+ * copy(dest, source, SHALLOW);
221
+ * dest.nested.push(3);
222
+ * console.log(source.nested); // [1, 2, 3] (source was modified)
442
223
  * ```
443
- * When *only* a function is given, `$` behaves exactly like {@link Store.observe},
444
- * except that it will only work when we're inside a `mount`.
224
+ */
225
+ export declare function copy<T extends object>(dst: T, src: T, flags?: number): void;
226
+ /** Flag to {@link copy} causing it to use merge semantics. See {@link copy} for details. */
227
+ export declare const MERGE = 1;
228
+ /** Flag to {@link copy} and {@link clone} causing them to create a shallow copy (instead of the deep copy done by default).*/
229
+ export declare const SHALLOW = 2;
230
+ /**
231
+ * Clone an (optionally proxied) object or array.
445
232
  *
446
- * @throws {ScopeError} If called outside an observable scope.
447
- * @throws {Error} If invalid arguments are provided.
233
+ * @param src The object or array to clone. If it is proxied, `clone` will subscribe to any changes to the (nested) data structure.
234
+ * @param flags
235
+ * - {@link SHALLOW}: Performs a shallow clone, meaning that only the top-level array or object will be copied, while object/array values will just be references to the original data in `src`.
236
+ * @template T - The type of the objects being copied.
237
+ * @returns A new unproxied array or object (of the same type as `src`), containing a deep (by default) copy of `src`.
448
238
  */
449
- export declare function $(...args: (string | (() => void) | false | null | undefined | {
450
- [key: string]: any;
451
- })[]): void;
239
+ export declare function clone<T extends object>(src: T, flags?: number): T;
452
240
  /**
453
- * Set a custome error handling function, thast is called when an error occurs during rendering
454
- * while in a reactive scope. The default implementation logs the error to the console, and then
455
- * just returns `true`, which causes an 'Error' message to be displayed in the UI. When this function
456
- * returns `false`, the error is suppressed. This mechanism exists because rendering errors can occur
457
- * at any time, not just synchronous when making a call to Aberdeen, thus normal exception handling
458
- * is not always possible.
241
+ * Creates a reactive reference (`{ value: T }`-like object) to a specific value
242
+ * within a proxied object or array.
243
+ *
244
+ * This is primarily used for the `bind` property in {@link $} to create two-way data bindings
245
+ * with form elements, and for passing a reactive property to any of the {@link $} key-value pairs.
459
246
  *
460
- * @param handler The handler function, getting an `Error` as its argument, and returning `false`
461
- * if it does *not* want an error message to be added to the DOM.
462
- * When `handler is `undefined`, the default error handling will be reinstated.
247
+ * Reading `ref.value` accesses the property from the underlying proxy (and subscribes the current scope).
248
+ * Assigning to `ref.value` updates the property in the underlying proxy (triggering reactive updates).
249
+ *
250
+ * @param target - The reactive proxy (created by {@link proxy}) containing the target property.
251
+ * @param index - The key (for objects) or index (for arrays) of the property to reference.
252
+ * @returns A reference object with a `value` property linked to the specified proxy property.
463
253
  *
464
254
  * @example
465
255
  * ```javascript
466
- * //
256
+ * const formData = proxy({ color: 'orange', velocity: 42 });
257
+ *
258
+ * // Usage with `bind`
259
+ * $('input', {
260
+ * type: 'text',
261
+ * // Creates a two-way binding between the input's value and formData.username
262
+ * bind: ref(formData, 'color')
263
+ * });
264
+ *
265
+ * // Usage as a dynamic property, causes a TextNode with just the name to be created and live-updated
266
+ * $('p:Selected color: ', {
267
+ * text: ref(formData, 'color'),
268
+ * $color: ref(formData, 'color')
269
+ * });
270
+ *
271
+ * // Changes are actually stored in formData - this causes logs like `{color: "Blue", velocity 42}`
272
+ * $(() => console.log(formData))
273
+ * ```
274
+ */
275
+ export declare function ref<T extends TargetType, K extends keyof T>(target: T, index: K): ValueRef<T[K]>;
276
+ /**
277
+ * The core function for building reactive user interfaces in Aberdeen. It creates and inserts new DOM elements
278
+ * and sets attributes/properties/event listeners on DOM elements. It does so in a reactive way, meaning that
279
+ * changes will be (mostly) undone when the current *scope* is destroyed or will be re-execute.
280
+ *
281
+ * @param {...(string | function | object | false | undefined | null)} args - Any number of arguments can be given. How they're interpreted depends on their types:
282
+ *
283
+ * - `string`: Strings can be used to create and insert new elements, set classnames for the *current* element, and add text to the current element.
284
+ * The format of a string is: **tag**? (`.` **class**)* (':' **text**)?
285
+ * meaning it consists of...
286
+ * - An optional HTML **tag**, something like `h1`. If present, a DOM element of that tag is created, and that element will be the *current* element for the rest of this `$` function execution.
287
+ * - Any number of CSS classes prefixed by `.` characters. These classes will be added to the *current* element.
288
+ * - Optional content **text** prefixed by a `:` character, ranging til the end of the string. This will be added as a TextNode to the *current* element.
289
+ * - `function`: When a function (without argument nor a return value) is passed in, it will be reactively executed in its own observe scope, preserving the *current element*. So any `$()` invocations within this function will create DOM elements with our *current* element as parent. If the function reads observable data, and that data is changed later on, the function we re-execute (after side effects, such as DOM modifications through `$`, have been cleaned - see also {@link clean}).
290
+ * - `object`: When an object is passed in, its key-value pairs are used to modify the *current* element in the following ways...
291
+ * - `{<attrName>: any}`: The common case is setting the value as an HTML attribute named key. So `{placeholder: "Your name"}` would add `placeholder="Your name"` to the current HTML element.
292
+ * - `{<propName>: boolean}` or `{value: any}` or `{selectedIndex: number}`: If the value is a boolean, or if the key is `value` or `selectedIndex`, it is set on the `current` element as a DOM property instead of an HTML attribute. For example `{checked: true}` would do `el.checked = true` for the *current* element.
293
+ * - `{".class": boolean}`: 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 `{".hidden": hide}` would toggle the `hidden` CSS class.
294
+ * - `{<eventName>: function}`: If the value is a `function` it is set as an event listener for the event with the name given by the key. For example: `{click: myClickHandler}`.
295
+ * - `{$<styleProp>: value}`: If the key starts with a `$` character, set a CSS style property with the name of the rest of the key to the given value. Example: `{$backgroundColor: 'red'}`.
296
+ * - `{create: string}`: Add the value string as a CSS class to the *current* element, *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`.
297
+ * - `{destroy: string}`: When the *current* element is a top-level element to be removed (due to reactivity cleanup), 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`.
298
+ * - `{create: function}` and `{destroy: function}`: The function is invoked when the *current* element is the top-level element being created/destroyed. It can be used for more involved creation/deletion animations. In case of `destroy`, the function is responsible for actually removing the element from the DOM (eventually). See `transitions.ts` in the Aberdeen source code for some examples.
299
+ * - `{bind: <obsValue>}`: Create a two-way binding element between the `value` property of the given observable (proxy) variable, and the *current* input element (`<input>`, `<select>` or `<textarea>`). This is often used together with {@link ref}, in order to use properties other than `.value`.
300
+ * - `{<any>: <obsvalue>}`: Create a new observe scope and read the `value` property of the given observable (proxy) variable from within it, and apply the contained value using any of the other rules in this list. Example:
301
+ * ```typescript
302
+ * const myColor = proxy('red');
303
+ * $('p:Test', {$color: myColor, click: () => myColor.value = 'yellow'})
304
+ * // Clicking the text will cause it to change color without recreating the <p> itself
305
+ * ```
306
+ * This is often used together with {@link ref}, in order to use properties other than `.value`.
307
+ * - `{text: string|number}`: Add the value as a `TextNode` to the *current* element.
308
+ * - `{html: string}`: Add the value as HTML to the *current* element. This should only be used in exceptional situations. And of course, beware of XSS.
309
+ * - `{element: Node}`: Add a pre-existing HTML `Node` to the *current* element.
310
+ *
311
+ *
312
+ * @example Create Element
313
+ * ```typescript
314
+ * $('button.secondary.outline:Submit', {
315
+ * disabled: true,
316
+ * click: () => console.log('Clicked!'),
317
+ * $color: 'red'
318
+ * });
319
+ * ```
320
+ *
321
+ * @example Nested Elements & Reactive Scope
322
+ * ```typescript
323
+ * const state = proxy({ count: 0 });
324
+ * $('div', () => { // Outer element
325
+ * // This scope re-renders when state.count changes
326
+ * $('p:Count is ${state.count}`);
327
+ * $('button:Increment', { click: () => state.count++ });
328
+ * });
329
+ * ```
330
+ *
331
+ * @example Two-way Binding
332
+ * ```typescript
333
+ * const user = proxy({ name: '' });
334
+ * $('input', { placeholder: 'Name', bind: ref(user, 'name') });
335
+ * $('h3', () => { // Reactive scope
336
+ * $(`:Hello ${user.name || 'stranger'}`);
337
+ * });
338
+ * ```
339
+ *
340
+ * @example Conditional Rendering
341
+ * ```typescript
342
+ * const show = proxy(false);
343
+ * $('button', { click: () => show.value = !show.value }, () => $(show.value ? ':Hide' : ':Show'));
344
+ * $(() => { // Reactive scope
345
+ * if (show.value) {
346
+ * $('p:Details are visible!');
347
+ * }
348
+ * });
349
+ * ```
350
+ */
351
+ export declare function $(...args: (string | null | undefined | false | (() => void) | Record<string, any>)[]): void;
352
+ /**
353
+ * Inserts CSS rules into the document, optionally scoping them with a unique class name.
354
+ *
355
+ * Takes a JavaScript object representation of CSS rules. camelCased property keys are
356
+ * converted to kebab-case (e.g., `fontSize` becomes `font-size`).
357
+ *
358
+ * @param style - An object where keys are CSS selectors (or camelCased properties) and values are
359
+ * CSS properties or nested rule objects.
360
+ * - Selectors are usually combined as a descendant-relationship (meaning just a space character) with their parent selector.
361
+ * - In case a selector contains a `&`, that character will be replaced by the parent selector.
362
+ * - Selectors will be split on `,` characters, each combining with the parent selector with *or* semantics.
363
+ * - Selector starting with `'@'` define at-rules like media queries. They may be nested within regular selectors.
364
+ * @param global - If `true`, styles are inserted globally without prefixing.
365
+ * If `false` (default), all selectors are prefixed with a unique generated
366
+ * class name (e.g., `.AbdStl1`) to scope the styles.
367
+ * @returns The unique class name prefix used for scoping (e.g., `.AbdStl1`), or an empty string
368
+ * if `global` was `true`. Use this prefix with {@link $} to apply the styles.
369
+ *
370
+ * @example Scoped Styles
371
+ * ```typescript
372
+ * const scopeClass = insertCss({
373
+ * color: 'red',
374
+ * padding: '10px',
375
+ * '&:hover': { // Use '&' for the root scoped selector
376
+ * backgroundColor: '#535'
377
+ * },
378
+ * '.child-element': { // Nested selector
379
+ * fontWeight: 'bold'
380
+ * },
381
+ * '@media (max-width: 600px)': {
382
+ * padding: '5px'
383
+ * }
384
+ * });
385
+ * // scopeClass might be ".AbdStl1"
386
+ *
387
+ * // Apply the styles
388
+ * $(scopeClass, () => { // Add class to the div
389
+ * $(`:Scoped content`);
390
+ * $('div.child-element:Child'); // .AbdStl1 .child-element rule applies
391
+ * });
392
+ * ```
393
+ *
394
+ * @example Global Styles
395
+ * ```typescript
396
+ * insertCss({
397
+ * '*': {
398
+ * fontFamily: 'monospace',
399
+ * },
400
+ * 'a': {
401
+ * textDecoration: 'none',
402
+ * color: "#107ab0",
403
+ * }
404
+ * }, true); // Pass true for global
405
+ *
406
+ * $('a:Styled link');
407
+ * ```
408
+ */
409
+ export declare function insertCss(style: object, global?: boolean): string;
410
+ /**
411
+ * Sets a custom error handler function for errors that occur asynchronously
412
+ * within reactive scopes (e.g., during updates triggered by proxy changes in
413
+ * {@link observe} or {@link $} render functions).
414
+ *
415
+ * The default handler logs the error to `console.error` and adds a simple
416
+ * 'Error' message div to the DOM at the location where the error occurred (if possible).
417
+ *
418
+ * Your handler can provide custom logging, UI feedback, or suppress the default
419
+ * error message.
420
+ *
421
+ * @param handler - A function that accepts the `Error` object.
422
+ * - Return `false` to prevent adding an error message to the DOM.
423
+ * - Return `true` or `undefined` (or throw) to allow the error messages to be added to the DOM.
424
+ *
425
+ * @example Custom Logging and Suppressing Default Message
426
+ * ```typescript
467
427
  * setErrorHandler(error => {
468
- * // Tell our developers about the problem.
469
- * fancyErrorLogger(error)
470
- * // Add custom error message to the DOM.
471
- * try {
472
- * $('.error:Sorry, something went wrong!')
473
- * } catch() {} // In case there is no parent element.
474
- * // Don't add default error message to the DOM.
475
- * return false
428
+ * console.warn('Aberdeen render error:', error.message);
429
+ * // Log to error reporting service
430
+ * // myErrorReporter.log(error);
431
+ *
432
+ * try {
433
+ * // Attempt to show a custom message in the UI
434
+ * $('div.error-message:Oops, something went wrong!');
435
+ * } catch (e) {
436
+ * // Ignore errors during error handling itself
437
+ * }
438
+ *
439
+ * return false; // Suppress default console log and DOM error message
440
+ * });
441
+ *
442
+ * // Styling for our custom error message
443
+ * insertCss({
444
+ * '.error-message': {
445
+ * backgroundColor: '#e31f00',
446
+ * display: 'inline-block',
447
+ * color: 'white',
448
+ * borderRadius: '3px',
449
+ * padding: '2px 4px',
450
+ * }
451
+ * }, true); // global style
452
+ *
453
+ * // Cause an error within a render scope.
454
+ * $('div.box', () => {
455
+ * // Will cause our error handler to insert an error message within the box
456
+ * noSuchFunction();
476
457
  * })
477
458
  * ```
478
459
  */
479
460
  export declare function setErrorHandler(handler?: (error: Error) => boolean | undefined): void;
480
461
  /**
481
- * Return the browser Element that nodes would be rendered to at this point.
482
- * NOTE: Manually changing the DOM is not recommended in most cases. There is
483
- * usually a better, declarative way. Although there are no hard guarantees on
484
- * how your changes interact with Aberdeen, in most cases results will not be
485
- * terribly surprising. Be careful within the parent element of onEach() though.
462
+ * Gets the parent DOM `Element` where nodes created by {@link $} would currently be inserted.
463
+ *
464
+ * This is context-dependent based on the current reactive scope (e.g., inside a {@link mount}
465
+ * call or a {@link $} element's render function).
466
+ *
467
+ * **Note:** While this provides access to the DOM element, directly manipulating it outside
468
+ * of Aberdeen's control is generally discouraged. Prefer declarative updates using {@link $}.
469
+ *
470
+ * @returns The current parent `Element` for DOM insertion.
471
+ *
472
+ * @example Get parent for attaching a third-party library
473
+ * ```typescript
474
+ * function thirdPartyLibInit(parentElement) {
475
+ * parentElement.innerHTML = "This element is managed by a <em>third party</em> lib."
476
+ * }
477
+ *
478
+ * $('div.box', () => {
479
+ * // Get the div.box element just created
480
+ * const containerElement = getParentElement();
481
+ * thirdPartyLibInit(containerElement);
482
+ * });
483
+ * ```
486
484
  */
487
485
  export declare function getParentElement(): Element;
488
486
  /**
489
- * Register a function that is to be executed right before the current reactive scope
490
- * disappears or redraws.
491
- * @param clean - The function to be executed.
487
+ * Registers a cleanup function to be executed just before the current reactive scope
488
+ * is destroyed or redraws.
489
+ *
490
+ * This is useful for releasing resources, removing manual event listeners, or cleaning up
491
+ * side effects associated with the scope. Cleaners are run in reverse order of registration.
492
+ *
493
+ * Scopes are created by functions like {@link observe}, {@link mount}, {@link $} (when given a render function),
494
+ * and internally by constructs like {@link onEach}.
495
+ *
496
+ * @param cleaner - The function to execute during cleanup.
497
+ *
498
+ * @example Maintaing a sum for a changing array
499
+ * ```typescript
500
+ * const myArray = proxy([3, 5, 10]);
501
+ * let sum = proxy(0);
502
+ *
503
+ * // Show the array items and maintain the sum
504
+ * onEach(myArray, (item, index) => {
505
+ * $(`code:${index}→${item}`);
506
+ * // We'll update sum.value using peek, as += first does a read, but
507
+ * // we don't want to subscribe.
508
+ * peek(() => sum.value += item);
509
+ * // Clean gets called before each rerun for a certain item index
510
+ * // No need for peek here, as the clean code doesn't run in an
511
+ * // observe scope.
512
+ * clean(() => sum.value -= item);
513
+ * })
514
+ *
515
+ * // Show the sum
516
+ * $('h1', {text: sum});
517
+ *
518
+ * // Make random changes to the array
519
+ * const rnd = () => 0|(Math.random()*20);
520
+ * setInterval(() => myArray[rnd()] = rnd(), 1000);
521
+ * ```
492
522
  */
493
- export declare function clean(clean: () => void): void;
523
+ export declare function clean(cleaner: () => void): void;
494
524
  /**
495
- * Reactively run a function, meaning the function will rerun when any `Store` that was read
496
- * during its execution is updated.
497
- * Calls to `observe` can be nested, such that changes to `Store`s read by the inner function do
498
- * no cause the outer function to rerun.
525
+ * Creates a reactive scope that automatically re-executes the provided function
526
+ * whenever any proxied data (created by {@link proxy}) read during its last execution changes, storing
527
+ * its return value in an observable.
499
528
  *
500
- * @param func - The function to be (repeatedly) executed.
501
- * @returns The mount id (usable for `unmount`) if this is a top-level observe.
502
- * @example
529
+ * Updates are batched and run asynchronously shortly after the changes occur.
530
+ * Use {@link clean} to register cleanup logic for the scope.
531
+ * Use {@link peek} or {@link unproxy} within the function to read proxied data without subscribing to it.
532
+ *
533
+ * @param func - The function to execute reactively. Any DOM manipulations should typically
534
+ * be done using {@link $} within this function. Its return value will be made available as an
535
+ * observable returned by the `observe()` function.
536
+ * @returns An observable object, with its `value` property containing whatever the last run of `func` returned.
537
+ *
538
+ * @example Observation creating a UI components
539
+ * ```typescript
540
+ * const data = proxy({ user: 'Frank', notifications: 42 });
541
+ *
542
+ * $('main', () => {
543
+ * console.log('Welcome');
544
+ * $('h3:Welcome, ' + data.user); // Reactive text
545
+ *
546
+ * observe(() => {
547
+ * // When data.notifications changes, only this inner scope reruns,
548
+ * // leaving the `<p>Welcome, ..</p>` untouched.
549
+ * console.log('Notifications');
550
+ * $('code.notification-badge:' + data.notifications);
551
+ * $('a:Notify!', {click: () => data.notifications++});
552
+ * });
553
+ * });
503
554
  * ```
504
- * let number = new Store(0)
505
- * let doubled = new Store()
506
- * setInterval(() => number.set(0|Math.random()*100)), 1000)
507
555
  *
508
- * observe(() => {
509
- * doubled.set(number.get() * 2)
510
- * })
556
+ * ***Note*** that the above could just as easily be done using `$(func)` instead of `observe(func)`.
511
557
  *
512
- * observe(() => {
513
- * console.log(doubled.get())
558
+ * @example Observation with return value
559
+ * ```typescript
560
+ * const counter = proxy(0);
561
+ * setInterval(() => counter.value++, 1000);
562
+ * const double = observe(() => counter.value * 2);
563
+ *
564
+ * $('h3', () => {
565
+ * $(`:counter=${counter.value} double=${double.value}`);
514
566
  * })
567
+ * ```
568
+ *
569
+ * @overload
570
+ * @param func Func without a return value.
515
571
  */
516
- export declare function observe(func: () => void): number | undefined;
517
- /**
518
- * Like `observe`, but instead of deferring running the observer function until
519
- * a setTimeout 0, run it immediately and synchronously when a change to one of
520
- * the observed `Store`s is made. Use this sparingly, as this prevents Aberdeen
521
- * from doing the usual batching and smart ordering of observers, leading to
522
- * performance problems and observing of 'weird' partial states.
523
- * @param func The function to be (repeatedly) executed.
524
- * @returns The mount id (usable for `unmount`) if this is a top-level observe.
525
- */
526
- export declare function immediateObserve(func: () => void): number | undefined;
572
+ export declare function observe<T extends (DatumType | void)>(func: () => T): ValueRef<T>;
527
573
  /**
528
- * Reactively run the function, adding any DOM-elements created using {@link $} to the given parent element.
529
-
530
- * @param func - The function to be (repeatedly) executed, possibly adding DOM elements to `parentElement`.
531
- * @param parentElement - A DOM element that will be used as the parent element for calls to `$`.
532
- * @returns The mount id (usable for `unmount`) if this is a top-level mount.
574
+ * Similar to {@link observe}, creates a reactive scope that re-executes the function
575
+ * when its proxied dependencies change.
576
+ *
577
+ * **Difference:** Updates run **synchronously and immediately** after the proxy modification
578
+ * that triggered the update occurs.
579
+ *
580
+ * **Caution:** Use sparingly. Immediate execution bypasses Aberdeen's usual batching and
581
+ * ordering optimizations, which can lead to performance issues or observing inconsistent
582
+ * intermediate states if multiple related updates are applied sequentially.
583
+ * Prefer {@link observe} or {@link $} for most use cases.
584
+ *
585
+ * @param func - The function to execute reactively and synchronously.
533
586
  *
534
587
  * @example
535
- * ```
536
- * let store = new Store(0)
537
- * setInterval(() => store.modify(v => v+1), 1000)
588
+ * ```javascript
589
+ * const state = proxy({ single: 'A' });
538
590
  *
539
- * mount(document.body, () => {
540
- * $(`h2:${store.get()} seconds have passed`)
541
- * })
542
- * ```
591
+ * immediateObserve(() => {
592
+ * state.double = state.single + state.single
593
+ * });
594
+ * console.log(state.double); // 'AA'
543
595
  *
544
- * An example nesting {@link Store.observe} within `mount`:
596
+ * state.single = 'B';
597
+ * // Synchronously:
598
+ * console.log(state.double); // 'BB'
545
599
  * ```
546
- * let selected = new Store(0)
547
- * let colors = new Store(new Map())
600
+ */
601
+ export declare function immediateObserve(func: () => void): void;
602
+ /**
603
+ * Attaches a reactive Aberdeen UI fragment to an existing DOM element. Without the use of
604
+ * this function, {@link $} will assume `document.body` as its root.
548
605
  *
549
- * mount(document.body, () => {
550
- * // This function will never rerun (as it does not read any `Store`s)
551
- * $('button:<<', {click: () => selected.modify(n => n-1)})
552
- * $('button:>>', {click: () => selected.modify(n => n+1)})
606
+ * It creates a top-level reactive scope associated with the `parentElement`. The provided
607
+ * function `func` is executed immediately within this scope. Any proxied data read by `func`
608
+ * will cause it to re-execute when the data changes, updating the DOM elements created within it.
553
609
  *
554
- * observe(() => {
555
- * // This will rerun whenever `selected` changes, recreating the <h2> and <input>.
556
- * $('h2', {text: '#' + selected.get()})
557
- * $('input', {type: 'color', value: '#ffffff' bind: colors(selected.get())})
558
- * })
610
+ * Calls to {@link $} inside `func` will append nodes to `parentElement`.
611
+ * You can nest {@link observe} or other {@link $} scopes within `func`.
612
+ * Use {@link unmountAll} to clean up all mounted scopes and their DOM nodes.
559
613
  *
560
- * observe(() => {
561
- * // This function will rerun when `selected` or the selected color changes.
562
- * // It will change the <body> background-color.
563
- * $({$backgroundColor: colors.get(selected.get()) || 'white'})
564
- * })
565
- * })
614
+ * Mounting scopes happens reactively, meaning that if this function is called from within another
615
+ * ({@link observe} or {@link $} or {@link mount}) scope that gets cleaned up, so will the mount.
616
+ *
617
+ * @param parentElement - The native DOM `Element` to which the UI fragment will be appended.
618
+ * @param func - The function that defines the UI fragment, typically containing calls to {@link $}.
619
+ *
620
+ * @example Basic Mount
621
+ * ```javascript
622
+ * // Create a pre-existing DOM structure (without Aberdeen)
623
+ * document.body.innerHTML = `<h3>Static content <span id="title-extra"></span></h3><div class="box" id="app-root"></div>`;
624
+ *
625
+ * import { mount, $, proxy } from 'aberdeen';
626
+ *
627
+ * const runTime = proxy(0);
628
+ * setInterval(() => runTime.value++, 1000);
629
+ *
630
+ * mount(document.getElementById('app-root'), () => {
631
+ * $('h4:Aberdeen App');
632
+ * $(`p:Run time: ${runTime.value}s`);
633
+ * // Conditionally render some content somewhere else in the static page
634
+ * if (runTime.value&1) {
635
+ * mount(document.getElementById('title-extra'), () =>
636
+ * $(`i:(${runTime.value}s)`)
637
+ * );
638
+ * }
639
+ * });
566
640
  * ```
567
- */
568
- export declare function mount(parentElement: Element, func: () => void): number | undefined;
641
+ *
642
+ * Note how the inner mount behaves reactively as well, automatically unmounting when it's parent observer scope re-runs.
643
+ */
644
+ export declare function mount(parentElement: Element, func: () => void): void;
569
645
  /**
570
- * Unmount one specific or all top-level mounts or observes, meaning those that were created outside of the scope
571
- * of any other mount or observe.
572
- * @param id Optional mount number (as returned by `mount`, `observe` or `immediateObserve`). If `undefined`, unmount all.
646
+ * Removes all Aberdeen-managed DOM nodes and stops all active reactive scopes
647
+ * (created by {@link mount}, {@link observe}, {@link $} with functions, etc.).
648
+ *
649
+ * This effectively cleans up the entire Aberdeen application state.
573
650
  */
574
- export declare function unmount(id?: number): void;
575
- /** Runs the given function, while not subscribing the current scope when reading {@link Store.Store} values.
651
+ export declare function unmountAll(): void;
652
+ /**
653
+ * Executes a function *without* creating subscriptions in the current reactive scope, and returns its result.
576
654
  *
577
- * @param func Function to be executed immediately.
578
- * @returns Whatever `func()` returns.
579
- * @example
580
- * ```
581
- * import {Store, peek, text} from aberdeen
655
+ * This is useful when you need to access reactive data inside a reactive scope (like {@link observe})
656
+ * but do not want changes to that specific data to trigger a re-execute of the scope.
582
657
  *
583
- * let store = new Store(['a', 'b', 'c'])
658
+ * @template T The type of the return value of your function.
584
659
  *
585
- * mount(document.body, () => {
586
- * // Prevent rerender when store changes
587
- * let msg = peek(() => `Store has ${store.count()} elements, and the first is ${store.get(0)}`))
588
- * text(msg)
589
- * })
660
+ * @param func - The function to execute without creating subscriptions.
661
+ * @returns Whatever `func` returns.
662
+ *
663
+ * @example Peeking within observe
664
+ * ```typescript
665
+ * const data = proxy({ a: 1, b: 2 });
666
+ * observe(() => {
667
+ * // re-executes only when data.a changes, because data.b is peeked.
668
+ * const b = peek(() => data.b);
669
+ * console.log(`A is ${data.a}, B was ${b} when A changed.`);
670
+ * });
671
+ * data.b = 3; // Does not trigger console.log
672
+ * data.a = 2; // Triggers console.log (logs "A is 2, B was 3 when A changed.")
590
673
  * ```
591
674
  *
592
- * In the above example `store.get(0)` could be replaced with `store.peek(0)` to achieve the
593
- * same result without `peek()` wrapping everything. There is no non-subscribing equivalent
594
- * for `count()` however.
595
675
  */
596
676
  export declare function peek<T>(func: () => T): T;
677
+ /** When using an object as `source`. */
678
+ export declare function map<IN, OUT>(source: Record<string | symbol, IN>, func: (value: IN, index: string | symbol) => undefined | OUT): Record<string | symbol, OUT>;
679
+ /** When using an array as `source`. */
680
+ export declare function map<IN, OUT>(source: Array<IN>, func: (value: IN, index: number) => undefined | OUT): Array<OUT>;
681
+ /** When using an array as `source`. */
682
+ export declare function multiMap<IN, OUT extends {
683
+ [key: string | symbol]: DatumType;
684
+ }>(source: Array<IN>, func: (value: IN, index: number) => OUT | undefined): OUT;
685
+ /** When using an object as `source`. */
686
+ export declare function multiMap<K extends string | number | symbol, IN, OUT extends {
687
+ [key: string | symbol]: DatumType;
688
+ }>(source: Record<K, IN>, func: (value: IN, index: K) => OUT | undefined): OUT;
689
+ /** When using an object as `array`. */
690
+ export declare function partition<OUT_K extends string | number | symbol, IN_V>(source: IN_V[], func: (value: IN_V, key: number) => undefined | OUT_K | OUT_K[]): Record<OUT_K, Record<number, IN_V>>;
691
+ /** When using an object as `source`. */
692
+ export declare function partition<IN_K extends string | number | symbol, OUT_K extends string | number | symbol, IN_V>(source: Record<IN_K, IN_V>, func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[]): Record<OUT_K, Record<IN_K, IN_V>>;
693
+ /**
694
+ * Renders a live, recursive dump of a proxied data structure (or any value)
695
+ * into the DOM at the current {@link $} insertion point.
696
+ *
697
+ * Uses `<ul>` and `<li>` elements to display object properties and array items.
698
+ * Updates reactively if the dumped data changes. Primarily intended for debugging purposes.
699
+ *
700
+ * @param data - The proxied data structure (or any value) to display.
701
+ * @returns The original `data` argument, allowing for chaining.
702
+ * @template T - The type of the data being dumped.
703
+ *
704
+ * @example Dumping reactive state
705
+ * ```typescript
706
+ * import { $, proxy, dump } from 'aberdeen';
707
+ *
708
+ * const state = proxy({
709
+ * user: { name: 'Frank', kids: 1 },
710
+ * items: ['a', 'b']
711
+ * });
712
+ *
713
+ * $('h2:Live State Dump');
714
+ * dump(state);
715
+ *
716
+ * // Change state later, the dump in the DOM will update
717
+ * setTimeout(() => { state.user.kids++; state.items.push('c'); }, 2000);
718
+ * ```
719
+ */
720
+ export declare function dump<T>(data: T): T;
597
721
  declare global {
598
722
  interface String {
599
723
  replaceAll(from: string, to: string): string;