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.
Files changed (139) hide show
  1. package/dist/src/aberdeen.d.ts +928 -944
  2. package/dist/src/aberdeen.js +1693 -1704
  3. package/dist/src/aberdeen.js.map +3 -3
  4. package/dist/src/index.d.ts +12 -0
  5. package/dist/src/index.js +11 -0
  6. package/dist/src/index.js.map +10 -0
  7. package/dist/src/prediction.d.ts +7 -4
  8. package/dist/src/prediction.js +18 -23
  9. package/dist/src/prediction.js.map +3 -3
  10. package/dist/src/route.d.ts +112 -99
  11. package/dist/src/route.js +254 -254
  12. package/dist/src/route.js.map +3 -3
  13. package/dist/src/server-dom.d.ts +158 -0
  14. package/dist/src/server-dom.js +458 -0
  15. package/dist/src/server-dom.js.map +10 -0
  16. package/dist/src/server.d.ts +55 -0
  17. package/dist/src/server.js +25 -0
  18. package/dist/src/server.js.map +10 -0
  19. package/dist/src/transitions.js +7 -4
  20. package/dist/src/transitions.js.map +3 -3
  21. package/dist/tests/environment.d.ts +24 -0
  22. package/dist/tests/{fakedom.js → environment.js} +239 -288
  23. package/dist/tests/environment.js.map +11 -0
  24. package/dist/tests/helpers.d.ts +15 -3
  25. package/dist/tests/helpers.js +277 -306
  26. package/dist/tests/helpers.js.map +6 -5
  27. package/dist-docs/Tutorial/index.html +30 -30
  28. package/dist-docs/aberdeen/Aberdeen/index.html +350 -0
  29. package/dist-docs/aberdeen/InitOptions/index.html +4 -0
  30. package/dist-docs/aberdeen/KeyToString/index.html +2 -0
  31. package/dist-docs/aberdeen/PromiseProxy/index.html +6 -6
  32. package/dist-docs/aberdeen/SortKeyType/index.html +2 -0
  33. package/dist-docs/aberdeen/TargetType/index.html +2 -0
  34. package/dist-docs/aberdeen/ValueRef/index.html +2 -0
  35. package/dist-docs/aberdeen/WindowLike/index.html +5 -0
  36. package/dist-docs/aberdeen/{clone → createA}/index.html +10 -5
  37. package/dist-docs/aberdeen/index.html +13 -54
  38. package/dist-docs/assets/aberdeen/aberdeen.d.ts +928 -944
  39. package/dist-docs/assets/aberdeen/aberdeen.js +1693 -1704
  40. package/dist-docs/assets/aberdeen/aberdeen.js.map +3 -3
  41. package/dist-docs/assets/aberdeen/index.d.ts +12 -0
  42. package/dist-docs/assets/aberdeen/index.js +11 -0
  43. package/dist-docs/assets/aberdeen/index.js.map +10 -0
  44. package/dist-docs/assets/aberdeen/prediction.d.ts +7 -4
  45. package/dist-docs/assets/aberdeen/prediction.js +18 -23
  46. package/dist-docs/assets/aberdeen/prediction.js.map +3 -3
  47. package/dist-docs/assets/aberdeen/route.d.ts +112 -99
  48. package/dist-docs/assets/aberdeen/route.js +254 -254
  49. package/dist-docs/assets/aberdeen/route.js.map +3 -3
  50. package/dist-docs/assets/aberdeen/server-dom.d.ts +158 -0
  51. package/dist-docs/assets/aberdeen/server-dom.js +458 -0
  52. package/dist-docs/assets/aberdeen/server-dom.js.map +10 -0
  53. package/dist-docs/assets/aberdeen/server.d.ts +55 -0
  54. package/dist-docs/assets/aberdeen/server.js +25 -0
  55. package/dist-docs/assets/aberdeen/server.js.map +10 -0
  56. package/dist-docs/assets/aberdeen/transitions.js +7 -4
  57. package/dist-docs/assets/aberdeen/transitions.js.map +3 -3
  58. package/dist-docs/assets/navigation.js +1 -1
  59. package/dist-docs/assets/search.js +1 -1
  60. package/dist-docs/dispatcher/Dispatcher/index.html +5 -6
  61. package/dist-docs/dispatcher/MATCH_FAILED/index.html +2 -2
  62. package/dist-docs/dispatcher/MATCH_REST/index.html +2 -2
  63. package/dist-docs/dispatcher/index.html +2 -2
  64. package/dist-docs/hierarchy.html +1 -1
  65. package/dist-docs/index-1/index.html +2 -0
  66. package/dist-docs/index.html +2 -2
  67. package/dist-docs/media/CHANGELOG.md +14 -2
  68. package/dist-docs/modules.html +1 -1
  69. package/dist-docs/prediction/applyCanon/index.html +4 -3
  70. package/dist-docs/prediction/applyPrediction/index.html +4 -3
  71. package/dist-docs/prediction/index.html +2 -2
  72. package/dist-docs/route/Route/index.html +9 -9
  73. package/dist-docs/route/RouteApi/index.html +60 -0
  74. package/dist-docs/route/index.html +6 -15
  75. package/dist-docs/route/init/index.html +5 -0
  76. package/dist-docs/server/RenderToStringOptions/index.html +6 -0
  77. package/dist-docs/server/RenderToStringResult/index.html +6 -0
  78. package/dist-docs/server/index.html +4 -0
  79. package/dist-docs/server/renderToString/index.html +6 -0
  80. package/dist-docs/sitemap.xml +51 -155
  81. package/dist-docs/transitions/grow/index.html +2 -2
  82. package/dist-docs/transitions/index.html +2 -2
  83. package/dist-docs/transitions/shrink/index.html +2 -2
  84. package/package.json +17 -7
  85. package/skill/SKILL.md +28 -28
  86. package/skill/aberdeen.md +471 -3448
  87. package/skill/dispatcher.md +6 -16
  88. package/skill/prediction.md +17 -5
  89. package/skill/route.md +95 -77
  90. package/skill/transitions.md +3 -3
  91. package/src/aberdeen.ts +1226 -740
  92. package/src/index.ts +13 -0
  93. package/src/prediction.ts +25 -24
  94. package/src/route.ts +192 -142
  95. package/src/server-dom.ts +508 -0
  96. package/src/server.ts +74 -0
  97. package/src/transitions.ts +9 -3
  98. package/dist/tests/fakedom.d.ts +0 -9
  99. package/dist/tests/fakedom.js.map +0 -10
  100. package/dist-docs/aberdeen/A/index.html +0 -115
  101. package/dist-docs/aberdeen/CUSTOM_DUMP/index.html +0 -7
  102. package/dist-docs/aberdeen/NO_COPY/index.html +0 -2
  103. package/dist-docs/aberdeen/OPAQUE/index.html +0 -12
  104. package/dist-docs/aberdeen/clean/index.html +0 -11
  105. package/dist-docs/aberdeen/copy/index.html +0 -22
  106. package/dist-docs/aberdeen/count/index.html +0 -7
  107. package/dist-docs/aberdeen/cssVars/index.html +0 -11
  108. package/dist-docs/aberdeen/darkMode/index.html +0 -9
  109. package/dist-docs/aberdeen/default/index.html +0 -341
  110. package/dist-docs/aberdeen/derive/index.html +0 -18
  111. package/dist-docs/aberdeen/disableCreateDestroy/index.html +0 -6
  112. package/dist-docs/aberdeen/dump/index.html +0 -11
  113. package/dist-docs/aberdeen/insertCss/index.html +0 -30
  114. package/dist-docs/aberdeen/insertGlobalCss/index.html +0 -19
  115. package/dist-docs/aberdeen/invertString/index.html +0 -13
  116. package/dist-docs/aberdeen/isEmpty/index.html +0 -12
  117. package/dist-docs/aberdeen/map/index.html +0 -26
  118. package/dist-docs/aberdeen/merge/index.html +0 -11
  119. package/dist-docs/aberdeen/mount/index.html +0 -17
  120. package/dist-docs/aberdeen/multiMap/index.html +0 -28
  121. package/dist-docs/aberdeen/onEach/index.html +0 -19
  122. package/dist-docs/aberdeen/partition/index.html +0 -36
  123. package/dist-docs/aberdeen/peek/index.html +0 -40
  124. package/dist-docs/aberdeen/proxy/index.html +0 -35
  125. package/dist-docs/aberdeen/ref/index.html +0 -13
  126. package/dist-docs/aberdeen/runQueue/index.html +0 -13
  127. package/dist-docs/aberdeen/setErrorHandler/index.html +0 -16
  128. package/dist-docs/aberdeen/setSpacingCssVars/index.html +0 -8
  129. package/dist-docs/aberdeen/unmountAll/index.html +0 -5
  130. package/dist-docs/aberdeen/unproxy/index.html +0 -11
  131. package/dist-docs/route/back/index.html +0 -4
  132. package/dist-docs/route/current/index.html +0 -2
  133. package/dist-docs/route/go/index.html +0 -14
  134. package/dist-docs/route/interceptLinks/index.html +0 -8
  135. package/dist-docs/route/matchCurrent/index.html +0 -10
  136. package/dist-docs/route/persistScroll/index.html +0 -6
  137. package/dist-docs/route/push/index.html +0 -6
  138. package/dist-docs/route/setLog/index.html +0 -3
  139. 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
- import type { ReverseSortedSetPointer } from "./helpers/reverseSortedSet.js";
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 Node) return sibling;
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 Node) return child;
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
- export function onEach<K, T>(
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
- export function onEach<T>(
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
- export function onEach<T>(
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
- export function onEach<K extends string | number | symbol, T>(
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
- export function onEach(
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
- export const EMPTY = Symbol("empty");
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
- /** @private */
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
- export function defaultEmitHandler(
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
- export function proxy<T extends any>(target: Promise<T>): PromiseProxy<T>;
1543
- export function proxy<T extends any>(target: Array<T>): Array<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T >;
1544
- export function proxy<T extends object>(target: T): T;
1545
- export function proxy<T extends any>(target: T): ValueRef<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T>;
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 A | A} or {@link derive}) establishes a subscription. Modifying properties *through*
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
- export function proxy(target: TargetType): TargetType {
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
- export function copy<T extends object>(dst: T, src: T): boolean;
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
- export function copy<T extends object>(dst: T, dstKey: keyof T, src: T[typeof dstKey]): boolean;
1716
- export function copy(a: any, b: any, c?: any): boolean {
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
- * Like {@link copy}, but uses merge semantics. Properties in `dst` not present in `src` are kept.
1735
- * `null`/`undefined` in `src` delete properties in `dst`.
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
- * A reactive object containing CSS variable definitions.
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
- * Initializes `cssVars[0]` through `cssVars[12]` with an exponential spacing scale.
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 (typeof window === 'undefined' || !window.matchMedia) return false;
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
- export function A(...args: any[]): undefined | Element {
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 A | A} (when given a render function),
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
- export function clean(cleaner: () => void) {
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 A | A} will assume `document.body` as its root.
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 A | A} inside `func` will append nodes to `parentElement`.
3000
- * You can nest {@link derive} or other {@link A | A} scopes within `func`.
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 A | A} or {@link mount}) scope that gets cleaned up, so will the mount.
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 A | A}.
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
- export function mount(parentElement: Element, func: () => void) {
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 A | A})
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
- export function peek<T extends object, K extends keyof T>(target: T, key: K): T[K];
3077
- export function peek<K,V>(target: Map<K,V>, key: K): V | undefined;
3078
- export function peek<T>(target: T[], key: number): T | undefined;
3079
- export function peek<T>(target: () => T): T;
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
- export function peek(target: any, key?: any) {
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
- export function map<K, IN, OUT>(
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
- export function map<IN, OUT>(
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
- export function map<IN, const IN_KEY extends string | number | symbol, OUT>(
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
- export function map(
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
- export function multiMap<IN, OUT extends { [key: string | symbol]: any }>(
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
- export function multiMap<
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
- export function multiMap<
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
- export function multiMap(
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
- export function partition<OUT_K extends string | number | symbol, IN_V>(
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
- export function partition<
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
- export function partition<
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
- export function partition<
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
- export function withEmitHandler(
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
- // Automatically add cssVars and cssSnippets (from insertCss) into a <head> style tag
3518
- if (typeof document !== "undefined") {
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
- if (isEmpty(cssVars)) return;
3529
- let css = ":root{";
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
- export default Object.assign(A, {
3560
- /** {@inheritDoc clean} */ clean,
3561
- /** {@inheritDoc clone} */ clone,
3562
- /** {@inheritDoc copy} */ copy,
3563
- /** {@inheritDoc count} */ count,
3564
- /** {@inheritDoc cssVars} */ cssVars,
3565
- /** {@inheritDoc CUSTOM_DUMP} */ CUSTOM_DUMP,
3566
- /** {@inheritDoc darkMode} */ darkMode,
3567
- /** {@inheritDoc derive} */ derive,
3568
- /** {@inheritDoc disableCreateDestroy} */ disableCreateDestroy,
3569
- /** {@inheritDoc dump} */ dump,
3570
- /** {@inheritDoc insertCss} */ insertCss,
3571
- /** {@inheritDoc insertGlobalCss} */ insertGlobalCss,
3572
- /** {@inheritDoc invertString} */ invertString,
3573
- /** {@inheritDoc isEmpty} */ isEmpty,
3574
- /** {@inheritDoc map} */ map,
3575
- /** {@inheritDoc merge} */ merge,
3576
- /** {@inheritDoc mount} */ mount,
3577
- /** {@inheritDoc multiMap} */ multiMap,
3578
- /** {@inheritDoc OPAQUE} */ OPAQUE,
3579
- /** {@inheritDoc NO_COPY} */ NO_COPY,
3580
- /** {@inheritDoc onEach} */ onEach,
3581
- /** {@inheritDoc partition} */ partition,
3582
- /** {@inheritDoc peek} */ peek,
3583
- /** {@inheritDoc proxy} */ proxy,
3584
- /** {@inheritDoc ref} */ ref,
3585
- /** {@inheritDoc runQueue} */ runQueue,
3586
- /** {@inheritDoc setErrorHandler} */ setErrorHandler,
3587
- /** {@inheritDoc setSpacingCssVars} */ setSpacingCssVars,
3588
- /** {@inheritDoc unmountAll} */ unmountAll,
3589
- /** {@inheritDoc unproxy} */ unproxy,
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
+ }