aberdeen 1.18.2 → 1.19.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 (152) hide show
  1. package/README.md +9 -0
  2. package/dist/src/aberdeen.d.ts +27 -0
  3. package/{dist-docs/assets/aberdeen/aberdeen.js → dist/src/aberdeen.dev.js} +776 -15
  4. package/dist/src/aberdeen.dev.js.map +12 -0
  5. package/dist/src/aberdeen.js +59 -13
  6. package/dist/src/aberdeen.js.map +3 -3
  7. package/dist/src/devtools.d.ts +26 -0
  8. package/html-to-aberdeen +9 -5
  9. package/package.json +17 -10
  10. package/skill/A.md +127 -0
  11. package/skill/Dispatcher.md +51 -0
  12. package/skill/OPAQUE.md +15 -0
  13. package/skill/PromiseProxy.md +25 -0
  14. package/skill/Route.md +50 -0
  15. package/skill/SKILL.md +272 -156
  16. package/skill/applyCanon.md +20 -0
  17. package/skill/applyPrediction.md +15 -0
  18. package/skill/back.md +11 -0
  19. package/skill/clean.md +43 -0
  20. package/skill/clone.md +15 -0
  21. package/skill/copy.md +40 -0
  22. package/skill/count.md +32 -0
  23. package/skill/cssVars.md +15 -0
  24. package/skill/darkMode.md +26 -0
  25. package/skill/derive.md +56 -0
  26. package/skill/developer-tools.md +23 -0
  27. package/skill/disableCreateDestroy.md +11 -0
  28. package/skill/dump.md +37 -0
  29. package/skill/freeze.md +20 -0
  30. package/skill/full-example-multi-page-app.md +146 -0
  31. package/skill/go.md +25 -0
  32. package/skill/grow.md +13 -0
  33. package/skill/html-to-aberdeen.md +9 -0
  34. package/skill/insertCss.md +79 -0
  35. package/skill/insertGlobalCss.md +68 -0
  36. package/skill/interceptLinks.md +19 -0
  37. package/skill/invertString.md +32 -0
  38. package/skill/isEmpty.md +38 -0
  39. package/skill/map.md +16 -0
  40. package/skill/matchCurrent.md +27 -0
  41. package/skill/merge.md +27 -0
  42. package/skill/mount.md +48 -0
  43. package/skill/multiMap.md +15 -0
  44. package/skill/onEach.md +14 -0
  45. package/skill/partition.md +15 -0
  46. package/skill/peek.md +36 -0
  47. package/skill/persistScroll.md +13 -0
  48. package/skill/proxy.md +11 -0
  49. package/skill/push.md +13 -0
  50. package/skill/ref.md +39 -0
  51. package/skill/runQueue.md +34 -0
  52. package/skill/setErrorHandler.md +48 -0
  53. package/skill/setLog.md +9 -0
  54. package/skill/setSpacingCssVars.md +31 -0
  55. package/skill/shrink.md +13 -0
  56. package/skill/unmountAll.md +9 -0
  57. package/skill/unproxy.md +43 -0
  58. package/skill/up.md +13 -0
  59. package/src/aberdeen.ts +194 -73
  60. package/src/devtools.ts +685 -0
  61. package/dist-docs/.nojekyll +0 -1
  62. package/dist-docs/Tutorial/index.html +0 -305
  63. package/dist-docs/aberdeen/A/index.html +0 -116
  64. package/dist-docs/aberdeen/CUSTOM_DUMP/index.html +0 -7
  65. package/dist-docs/aberdeen/NO_COPY/index.html +0 -2
  66. package/dist-docs/aberdeen/OPAQUE/index.html +0 -12
  67. package/dist-docs/aberdeen/PromiseProxy/index.html +0 -8
  68. package/dist-docs/aberdeen/clean/index.html +0 -11
  69. package/dist-docs/aberdeen/clone/index.html +0 -5
  70. package/dist-docs/aberdeen/copy/index.html +0 -22
  71. package/dist-docs/aberdeen/count/index.html +0 -7
  72. package/dist-docs/aberdeen/cssVars/index.html +0 -11
  73. package/dist-docs/aberdeen/darkMode/index.html +0 -9
  74. package/dist-docs/aberdeen/default/index.html +0 -342
  75. package/dist-docs/aberdeen/derive/index.html +0 -18
  76. package/dist-docs/aberdeen/disableCreateDestroy/index.html +0 -6
  77. package/dist-docs/aberdeen/dump/index.html +0 -11
  78. package/dist-docs/aberdeen/index.html +0 -54
  79. package/dist-docs/aberdeen/insertCss/index.html +0 -30
  80. package/dist-docs/aberdeen/insertGlobalCss/index.html +0 -19
  81. package/dist-docs/aberdeen/invertString/index.html +0 -13
  82. package/dist-docs/aberdeen/isEmpty/index.html +0 -12
  83. package/dist-docs/aberdeen/map/index.html +0 -26
  84. package/dist-docs/aberdeen/merge/index.html +0 -11
  85. package/dist-docs/aberdeen/mount/index.html +0 -17
  86. package/dist-docs/aberdeen/multiMap/index.html +0 -28
  87. package/dist-docs/aberdeen/onEach/index.html +0 -19
  88. package/dist-docs/aberdeen/partition/index.html +0 -36
  89. package/dist-docs/aberdeen/peek/index.html +0 -40
  90. package/dist-docs/aberdeen/proxy/index.html +0 -35
  91. package/dist-docs/aberdeen/ref/index.html +0 -13
  92. package/dist-docs/aberdeen/runQueue/index.html +0 -13
  93. package/dist-docs/aberdeen/setErrorHandler/index.html +0 -16
  94. package/dist-docs/aberdeen/setSpacingCssVars/index.html +0 -8
  95. package/dist-docs/aberdeen/unmountAll/index.html +0 -5
  96. package/dist-docs/aberdeen/unproxy/index.html +0 -11
  97. package/dist-docs/assets/aberdeen/aberdeen.d.ts +0 -963
  98. package/dist-docs/assets/aberdeen/aberdeen.js.map +0 -11
  99. package/dist-docs/assets/aberdeen/dispatcher.d.ts +0 -57
  100. package/dist-docs/assets/aberdeen/dispatcher.js +0 -66
  101. package/dist-docs/assets/aberdeen/dispatcher.js.map +0 -10
  102. package/dist-docs/assets/aberdeen/helpers/reverseSortedSet.d.ts +0 -96
  103. package/dist-docs/assets/aberdeen/prediction.d.ts +0 -33
  104. package/dist-docs/assets/aberdeen/prediction.js +0 -112
  105. package/dist-docs/assets/aberdeen/prediction.js.map +0 -10
  106. package/dist-docs/assets/aberdeen/route.d.ts +0 -137
  107. package/dist-docs/assets/aberdeen/route.js +0 -287
  108. package/dist-docs/assets/aberdeen/route.js.map +0 -10
  109. package/dist-docs/assets/aberdeen/transitions.d.ts +0 -18
  110. package/dist-docs/assets/aberdeen/transitions.js +0 -47
  111. package/dist-docs/assets/aberdeen/transitions.js.map +0 -10
  112. package/dist-docs/assets/custom.js +0 -573
  113. package/dist-docs/assets/favicon.png +0 -0
  114. package/dist-docs/assets/hierarchy.js +0 -1
  115. package/dist-docs/assets/highlight.css +0 -134
  116. package/dist-docs/assets/icons.js +0 -18
  117. package/dist-docs/assets/icons.svg +0 -1
  118. package/dist-docs/assets/main.js +0 -60
  119. package/dist-docs/assets/navigation.js +0 -1
  120. package/dist-docs/assets/search.js +0 -1
  121. package/dist-docs/assets/style.css +0 -1640
  122. package/dist-docs/dispatcher/Dispatcher/index.html +0 -21
  123. package/dist-docs/dispatcher/MATCH_FAILED/index.html +0 -2
  124. package/dist-docs/dispatcher/MATCH_REST/index.html +0 -2
  125. package/dist-docs/dispatcher/index.html +0 -4
  126. package/dist-docs/hierarchy.html +0 -1
  127. package/dist-docs/index.html +0 -61
  128. package/dist-docs/media/CHANGELOG.md +0 -266
  129. package/dist-docs/modules.html +0 -1
  130. package/dist-docs/prediction/applyCanon/index.html +0 -13
  131. package/dist-docs/prediction/applyPrediction/index.html +0 -8
  132. package/dist-docs/prediction/index.html +0 -9
  133. package/dist-docs/route/Route/index.html +0 -23
  134. package/dist-docs/route/back/index.html +0 -4
  135. package/dist-docs/route/current/index.html +0 -2
  136. package/dist-docs/route/go/index.html +0 -14
  137. package/dist-docs/route/index.html +0 -15
  138. package/dist-docs/route/interceptLinks/index.html +0 -8
  139. package/dist-docs/route/matchCurrent/index.html +0 -10
  140. package/dist-docs/route/persistScroll/index.html +0 -6
  141. package/dist-docs/route/push/index.html +0 -6
  142. package/dist-docs/route/setLog/index.html +0 -3
  143. package/dist-docs/route/up/index.html +0 -5
  144. package/dist-docs/sitemap.xml +0 -239
  145. package/dist-docs/transitions/grow/index.html +0 -6
  146. package/dist-docs/transitions/index.html +0 -5
  147. package/dist-docs/transitions/shrink/index.html +0 -6
  148. package/skill/aberdeen.md +0 -4586
  149. package/skill/dispatcher.md +0 -129
  150. package/skill/prediction.md +0 -73
  151. package/skill/route.md +0 -322
  152. package/skill/transitions.md +0 -59
@@ -0,0 +1,26 @@
1
+ ## darkMode · function
2
+
3
+ Returns whether the user's browser prefers a dark color scheme.
4
+
5
+ This function is reactive - scopes that call it will re-execute when the
6
+ browser's color scheme preference changes (via the `prefers-color-scheme` media query).
7
+
8
+ Use this in combination with | A and `cssVars` to implement theme switching:
9
+
10
+ **Signature:** `() => boolean`
11
+
12
+ **Returns:** `true` if the browser prefers dark mode, `false` if it prefers light mode.
13
+
14
+ **Examples:**
15
+
16
+ ```javascript
17
+ import A from 'aberdeen';
18
+
19
+ // Reactively set colors based on browser preference
20
+ A(() => {
21
+ A.cssVars.bg = A.darkMode() ? '#1a1a1a' : '#ffffff';
22
+ A.cssVars.fg = A.darkMode() ? '#e5e5e5' : '#000000';
23
+ });
24
+
25
+ A('div bg:$bg fg:$fg p:1rem #Colors change based on system dark mode preference');
26
+ ```
@@ -0,0 +1,56 @@
1
+ ## derive · function
2
+
3
+ Creates a reactive scope that automatically re-executes the provided function
4
+ whenever any proxied data (created by `proxy`) read during its last execution changes, storing
5
+ its return value in an observable.
6
+
7
+ Updates are batched and run asynchronously shortly after the changes occur.
8
+ Use `clean` to register cleanup logic for the scope.
9
+ Use `peek` or `unproxy` within the function to read proxied data without subscribing to it.
10
+
11
+ **Signature:** `<T>(func: () => T) => ValueRef<T>`
12
+
13
+ **Type Parameters:**
14
+
15
+ - `T`
16
+
17
+ **Parameters:**
18
+
19
+ - `func: () => T` - - The function to execute reactively. Any DOM manipulations should typically
20
+ be done using | A within this function. Its return value will be made available as an
21
+ observable returned by the `derive()` function.
22
+
23
+ **Returns:** An observable object, with its `value` property containing whatever the last run of `func` returned.
24
+
25
+ **Examples:**
26
+
27
+ Observation creating UI components
28
+ ```typescript
29
+ const $data = A.proxy({ user: 'Frank', notifications: 42 });
30
+
31
+ A('main', () => {
32
+ console.log('Welcome');
33
+ A('h3#Welcome, ' + $data.user); // Reactive text
34
+
35
+ A.derive(() => {
36
+ // When $data.notifications changes, only this inner scope reruns,
37
+ // leaving the `<p>Welcome, ..</p>` untouched.
38
+ console.log('Notifications');
39
+ A('code.notification-badge text=', $data.notifications);
40
+ A('a text=Notify! click=', () => $data.notifications++);
41
+ });
42
+ });
43
+ ```
44
+
45
+ ***Note*** that the above could just as easily be done using `A(func)` instead of `derive(func)`.
46
+
47
+ Observation with return value
48
+ ```typescript
49
+ const $counter = A.proxy(0);
50
+ setInterval(() => $counter.value++, 1000);
51
+ const $double = A.derive(() => $counter.value * 2);
52
+
53
+ A('h3', () => {
54
+ A(`#counter=${$counter.value} double=${$double.value}`);
55
+ })
56
+ ```
@@ -0,0 +1,23 @@
1
+ ## Developer tools
2
+
3
+ Aberdeen has an in-browser inspector for its reactive scope tree. The live examples on this page support it. **Click inside an example's 'Browser' tab to focus it, then press `Ctrl/Cmd+Alt+A`** to open the tools (press again to close). Try it with this one:
4
+
5
+ ```typescript
6
+ import A from 'aberdeen';
7
+
8
+ const $state = A.proxy({fruits: ['apple', 'banana']});
9
+
10
+ A('button text="Add cherry" click=', () => $state.fruits.push('cherry'));
11
+ A('ul', () => {
12
+ A.onEach($state.fruits, fruit => A('li text=', fruit));
13
+ });
14
+ ```
15
+
16
+ A panel opens with two columns. The **left** is the live scope tree — every reactive block (the `<ul>`'s render function, the `onEach`, each of its items, ...), colour-coded by type and labelled with the `file:line` where it was created. The **right** shows details for the selected scope: its DOM nodes and child scopes, what it's observing, where it was created, and which change last triggered it to re-run.
17
+
18
+ To use it:
19
+ - **Click a scope** in the tree to select it. Its container element (which may contain more than one scope) is outlined in orange, the top-level elements it created in green.
20
+ - **Hold `Alt` and hover** any element to select the scope that drew it.
21
+ - Hit **Add cherry** and switch to the **Recent** tab to watch the re-render stream in, or toggle **Freeze** to pause re-renders while you look around.
22
+
23
+ This is a development-only aid and is stripped from production builds. By default it's part of Aberdeen's **development** build, which bundlers like Vite select automatically in dev mode (via the `development` export condition) while production builds get the bare core. If you'd rather not rely on bundler settings, `import A from 'aberdeen/tooled'` to *always* include the dev tools, or `aberdeen/untooled` to *always* leave them out. Both expose the exact same API as `aberdeen`.
@@ -0,0 +1,11 @@
1
+ ## disableCreateDestroy · function
2
+
3
+ Make the `create` and `destroy` special properties no-ops.
4
+
5
+ This is useful from within automated testing environments, where the transitioning
6
+ new and lingering old elements may make writing reliable selectors difficult.
7
+
8
+ As this is only intended for testing, there's no way to re-enable the special properties
9
+ once disabled.
10
+
11
+ **Signature:** `() => void`
package/skill/dump.md ADDED
@@ -0,0 +1,37 @@
1
+ ## dump · function
2
+
3
+ Renders a live, recursive dump of a proxied data structure (or any value)
4
+ into the DOM at the current | A insertion point.
5
+
6
+ Uses `<ul>` and `<li>` elements to display object properties and array items.
7
+ Updates reactively if the dumped data changes. Primarily intended for debugging purposes.
8
+
9
+ **Signature:** `<T>(data: T) => T`
10
+
11
+ **Type Parameters:**
12
+
13
+ - `T` - The type of the data being dumped.
14
+
15
+ **Parameters:**
16
+
17
+ - `data: T` - - The proxied data structure (or any value) to display.
18
+
19
+ **Returns:** The original `data` argument, allowing for chaining.
20
+
21
+ **Examples:**
22
+
23
+ Dumping reactive state
24
+ ```typescript
25
+ import A from 'aberdeen';
26
+
27
+ const $state = A.proxy({
28
+ user: { name: 'Frank', kids: 1 },
29
+ items: ['a', 'b']
30
+ });
31
+
32
+ A('h2#Live State Dump');
33
+ A.dump($state);
34
+
35
+ // Change state later, the dump in the DOM will update
36
+ setTimeout(() => { $state.user.kids++; $state.items.push('c'); }, 2000);
37
+ ```
@@ -0,0 +1,20 @@
1
+ ## freeze · function
2
+
3
+ Pause processing of reactive updates until the returned *thaw* function is called.
4
+
5
+ While frozen, changes to observed data still accumulate, but no re-renders run. Freezes
6
+ stack: if there are multiple outstanding freezes, redraws resume only once the last one is
7
+ thawed. This is useful to batch an async burst of changes into a single update pass, or to
8
+ hold the UI steady (e.g. the dev tools use it for "freeze redraws").
9
+
10
+ **Signature:** `() => () => void`
11
+
12
+ **Returns:** A function that releases this freeze. Calling it more than once has no effect.
13
+
14
+ **Examples:**
15
+
16
+ ```typescript
17
+ const thaw = A.freeze();
18
+ // ...make many changes without intermediate redraws...
19
+ thaw(); // redraws run now (if no other freezes remain)
20
+ ```
@@ -0,0 +1,146 @@
1
+ ## Full Example: Multi-page App
2
+
3
+ Here's a complete example (a contact manager) demonstrating routing, state management, CSS, dark mode, and dynamic content:
4
+
5
+ ```typescript
6
+ import A from 'aberdeen';
7
+ import * as route from 'aberdeen/route';
8
+ import { Dispatcher } from 'aberdeen/dispatcher';
9
+ import { grow, shrink } from 'aberdeen/transitions';
10
+
11
+ class Contact {
12
+ constructor(
13
+ public id: number,
14
+ public firstName: string,
15
+ public lastName: string,
16
+ public email: string,
17
+ public phone: string
18
+ ) {}
19
+ }
20
+
21
+ // Enable link interception for SPA navigation
22
+ route.interceptLinks();
23
+
24
+ // Initialize $1-$12 CSS variables for consistent spacing ($2=0.5rem, $3=1rem, $4=2rem, etc.)
25
+ A.setSpacingCssVars();
26
+
27
+ // Reactive theme based on system preference
28
+ A(() => {
29
+ A.cssVars.primary = '#2563eb';
30
+ A.cssVars.bg = A.darkMode() ? '#0f172a' : '#ffffff';
31
+ A.cssVars.fg = A.darkMode() ? '#e2e8f0' : '#1e293b';
32
+ A.cssVars.cardBg = A.darkMode() ? '#1e293b' : '#f8fafc';
33
+ A.cssVars.border = A.darkMode() ? '#334155' : '#e2e8f0';
34
+ });
35
+
36
+ // Global styles for semantic HTML elements that apply everywhere
37
+ A.insertGlobalCss({
38
+ "*": "m:0 p:0",
39
+ "body": "bg:$bg fg:$fg font-family: system-ui, sans-serif;",
40
+ "a": "color:$primary text-decoration:none",
41
+ "a:hover": "text-decoration:underline",
42
+ "a[role=button]": "bg:$primary fg:white r:8px p:$2",
43
+ });
44
+
45
+ // Application state
46
+ const $contacts = A.proxy([
47
+ new Contact(1, 'Emma', 'Wilson', 'emma.wilson@email.com', '555-0101'),
48
+ new Contact(2, 'James', 'Anderson', 'j.anderson@email.com', '555-0102'),
49
+ new Contact(3, 'Sofia', 'Martinez', 'sofia.m@email.com', '555-0103'),
50
+ new Contact(4, 'Liam', 'Brown', 'liam.brown@email.com', '555-0104')
51
+ ]);
52
+
53
+ // Router setup
54
+ const dispatcher = new Dispatcher();
55
+ dispatcher.addRoute(drawHome);
56
+ dispatcher.addRoute('contacts', drawContactList);
57
+ dispatcher.addRoute('contacts', Number, drawContactDetail);
58
+
59
+ // Main app
60
+ A('div.app', () => {
61
+ A('nav display:flex gap:$3 p:$3 border-bottom: 1px solid $border;', () => {
62
+ A('a href=/ text=Home font-weight:', route.current.p.length === 0 ? 'bold' : 'normal');
63
+ A('a href=/contacts text=Contacts font-weight:', route.current.p[0] === 'contacts' ? 'bold' : 'normal');
64
+ });
65
+ A('main p:$3', () => dispatcher.dispatch(route.current.p));
66
+ });
67
+
68
+ function drawHome() {
69
+ A('h1#Contact Manager');
70
+ A('p#A modern contact list with search, sort, and dark mode support.');
71
+ }
72
+
73
+ // Contact card styles
74
+ const cardStyle = A.insertCss({
75
+ "&": "bg:$cardBg border: 1px solid $border; r:8px p:$3 mv:$2 display:block transition: transform 0.2s;",
76
+ "&:hover": "transform:translateX(4px)",
77
+ "a&": "color:inherit;",
78
+ });
79
+
80
+ const filterStyle = A.insertCss({
81
+ "&": "display:flex gap:$3 mv:$3",
82
+ "> *": "p:$2 r:4px bg:$bg fg:$fg border: 1px solid $border;",
83
+ });
84
+
85
+ function drawContactList() {
86
+ A('h1#Contacts');
87
+
88
+ // Search and sort controls
89
+ A('div', filterStyle, () => {
90
+ A('input flex:1 placeholder="Search contacts..." bind=', A.ref(route.current.search, 'q'));
91
+ A('select bind=', A.ref(route.current.search, 'sort'), () => {
92
+ A('option value=firstName #First Name');
93
+ A('option value=lastName #Last Name');
94
+ A('option value=email #Email');
95
+ });
96
+ });
97
+
98
+ // Contact list
99
+ A('div', () => {
100
+ const sortBy = route.current.search.sort || 'firstName';
101
+
102
+ const $filtered = A.map($contacts, $contact => {
103
+ const query = route.current.search.q;
104
+ if (query) {
105
+ const info = `${$contact.firstName} ${$contact.lastName} ${$contact.email}`;
106
+ if (!info.toLowerCase().includes(query.toLowerCase())) return; // Skip!
107
+ }
108
+ return $contact;
109
+ });
110
+
111
+ A.onEach($filtered, $contact => {
112
+ A('a', cardStyle, 'create=', grow, 'destroy=', shrink, `href=/contacts/${$contact.id}`, () => {
113
+ A('h2', () => {
114
+ A('span font-weight:normal text=', $contact.firstName+" ");
115
+ A('span text=', $contact.lastName);
116
+ });
117
+ A('div text=', $contact.email);
118
+ });
119
+ }, $contact => $contact[sortBy].toLowerCase());
120
+
121
+ A(`a role=button mt:$3 text="Add new contact" href=/contacts/${$contacts.length}`);
122
+ });
123
+ }
124
+
125
+ // Detail form styles
126
+ const detailStyle = A.insertCss({
127
+ "&": "bg:$cardBg border: 1px solid $border; r:8px p:$4 max-width:600px",
128
+ "label": "display:block font-weight:600 mt:$3 mb:$2",
129
+ "input": "w:100% p:$2 r:4px border: 1px solid $border; bg:$bg fg:$fg"
130
+ });
131
+
132
+ function drawContactDetail(id: number) {
133
+ $contacts[id] ||= new Contact(id, '', '', '', '');
134
+ const $contact = $contacts[id];
135
+
136
+ A('a role=button href=/contacts #← Back');
137
+
138
+ A('div mt:$3', detailStyle, () => {
139
+ A('h2 mb:$2 text=', A.ref($contact, 'firstName'), 'text=', ' ', 'text=', A.ref($contact, 'lastName'));
140
+ A('label text="First Name" input bind=', A.ref($contact, 'firstName'));
141
+ A('label text="Last Name" input bind=', A.ref($contact, 'lastName'));
142
+ A('label text="Email" input type=email bind=', A.ref($contact, 'email'));
143
+ A('label text="Phone" input type=tel bind=', A.ref($contact, 'phone'));
144
+ });
145
+ }
146
+ ```
package/skill/go.md ADDED
@@ -0,0 +1,25 @@
1
+ ## go · function
2
+
3
+ Navigate to a new URL by pushing a new history entry.
4
+
5
+ Note that this happens synchronously, immediately updating `route` and processing any reactive updates based on that.
6
+
7
+ **Signature:** `(target: RouteTarget, nav?: NavType) => void`
8
+
9
+ **Parameters:**
10
+
11
+ - `target: RouteTarget` - A subset of the `Route` properties to navigate to. If neither `p` nor `path` is given, the current path is used. For other properties, an empty/default value is assumed if not given. For convenience:
12
+ - You may pass a string instead of an object, which is interpreted as the `path`.
13
+ - You may pass an array instead of an object, which is interpreted as the `p` array.
14
+ - If you pass `p`, it may contain numbers, which will be converted to strings.
15
+ - If you pass `search`, its values may be numbers, which will be converted to strings.
16
+
17
+ Examples:
18
+ ```js
19
+ // Navigate to /users/123
20
+ route.go("/users/123");
21
+
22
+ // Navigate to /users/123?tab=feed#top
23
+ route.go({p: ["users", 123], search: {tab: "feed"}, hash: "top"});
24
+ ```
25
+ - `nav: NavType` (optional)
package/skill/grow.md ADDED
@@ -0,0 +1,13 @@
1
+ ## grow · function
2
+
3
+ Do a grow transition for the given element. This is meant to be used as a
4
+ handler for the `create` property.
5
+
6
+ **Signature:** `(el: HTMLElement) => Promise<void>`
7
+
8
+ **Parameters:**
9
+
10
+ - `el: HTMLElement` - The element to transition.
11
+
12
+ The transition doesn't look great for table elements, and may have problems
13
+ for other specific cases as well.
@@ -0,0 +1,9 @@
1
+ ## html-to-aberdeen
2
+
3
+ This tool allows you to convert a block of HTML into Aberdeen syntax.
4
+
5
+ ```sh
6
+ npx html-to-aberdeen
7
+ ```
8
+
9
+ It takes HTML on stdin (paste it and press `ctrl-d` for end-of-file), and outputs JavaScript on stdout.
@@ -0,0 +1,79 @@
1
+ ## insertCss · function
2
+
3
+ Inserts CSS rules into the document, scoping them with a unique class name.
4
+
5
+ The `style` parameter can be either:
6
+ - A **concise style string** (for rules applying to the root class).
7
+ - An **object** where keys are selectors (with `&` representing the root class)
8
+ and values are concise style strings or nested objects. When the key does not contain `&`,
9
+ it is treated as a descendant selector. So `{p: "color:red"}` becomes `".AbdStlX p { color: red; }"` with `AbdStlX` being the generated class name.
10
+
11
+ ### Concise Style Strings
12
+
13
+ Concise style strings use two syntaxes (same as inline CSS in | A):
14
+ - **Short form** `key:value` (no space after colon): The value ends at the next whitespace.
15
+ Example: `'m:$3 bg:red r:8px'`
16
+ - **Long form** `key: value;` (space after colon): The value continues until a semicolon.
17
+ Example: `'box-shadow: 2px 0 6px black; transition: all 0.3s ease;'`
18
+
19
+ Both forms can be mixed: `'m:$3 box-shadow: 0 2px 4px rgba(0,0,0,0.2); bg:$cardBg'`
20
+
21
+ Supports the same CSS shortcuts as | A and CSS variable references with `$` (e.g., `$primary`, `$3`).
22
+
23
+ CSS is inserted into the <head> in an order relative to other insert(Global)Css items that is consistent
24
+ based on when the containing Aberdeen scope was first defined. This allows changing styles without changing
25
+ order-based precedence.
26
+
27
+ **Signature:** `(style: string | object) => string`
28
+
29
+ **Parameters:**
30
+
31
+ - `style: string | object` - - A concise style string or a style object.
32
+
33
+ **Returns:** The unique class name prefix used for scoping (e.g., `.AbdStl1`).
34
+ Use this prefix with | A to apply the styles.
35
+
36
+ **Examples:**
37
+
38
+ Basic Usage with Shortcuts and CSS Variables
39
+ ```typescript
40
+ const cardClass = A.insertCss({
41
+ '&': 'bg:white p:$4 r:8px transition: background-color 0.3s;',
42
+ '&:hover': 'bg:#f5f5f5',
43
+ });
44
+
45
+ A('section', cardClass, () => {
46
+ A('p#Card content');
47
+ });
48
+ ```
49
+
50
+ Nested Selectors and Media Queries
51
+ ```typescript
52
+ const formClass = A.insertCss({
53
+ '&': 'bg:#0004 p:$3 r:$2',
54
+ button: {
55
+ '&': 'bg:$primary fg:white p:$2 r:4px cursor:pointer',
56
+ '&:hover': 'bg:$primaryHover',
57
+ '&:disabled': 'bg:#ccc cursor:not-allowed',
58
+ '.icon': 'display:inline-block mr:$1',
59
+ '@media (max-width: 600px)': 'p:$1 font-size:14px'
60
+ }
61
+ });
62
+
63
+ A('form', formClass, () => {
64
+ A('button', () => {
65
+ A('span.icon text=🔥');
66
+ A('#Click Me');
67
+ });
68
+ });
69
+ ```
70
+
71
+ Complex CSS Values
72
+ ```typescript
73
+ const badge = A.insertCss({
74
+ '&::before': 'content: "★"; color:gold mr:$1',
75
+ '&': 'position:relative box-shadow: 0 2px 8px rgba(0,0,0,0.15);'
76
+ });
77
+
78
+ A(badge + ' span#Product Name');
79
+ ```
@@ -0,0 +1,68 @@
1
+ ## insertGlobalCss · function
2
+
3
+ Inserts CSS rules globally (unscoped).
4
+
5
+ Works exactly like `insertCss`, but without prefixing selectors with a unique class name.
6
+ This is useful for global resets, base styles, or styles that need to apply to the entire document.
7
+
8
+ Accepts the same concise style string syntax and CSS shortcuts as `insertCss`.
9
+ See `insertCss` for detailed documentation on syntax and shortcuts.
10
+
11
+ **Signature:** `(style: object) => void`
12
+
13
+ **Parameters:**
14
+
15
+ - `style: object` - - Object with selectors as keys and concise CSS strings as values.
16
+
17
+ CSS is inserted into the <head> in an order relative to other insert(Global)Css items that is consistent
18
+ based on when the containing Aberdeen scope was first defined. This allows changing styles without changing
19
+ order-based precedence.
20
+
21
+ **Examples:**
22
+
23
+ Global Reset and Base Styles
24
+ ```typescript
25
+ // Set up global styles using CSS shortcuts
26
+ A.insertGlobalCss({
27
+ "*": "m:0 p:0 box-sizing:border-box",
28
+ "body": "font-family: system-ui, sans-serif; m:0 p:$3 bg:#434 fg:#d0dafa",
29
+ "a": "text-decoration:none fg:#57f",
30
+ "a:hover": "text-decoration:underline",
31
+ "code": "font-family:monospace bg:#222 fg:#afc p:4px r:3px"
32
+ });
33
+
34
+ A('h2#Title without margins');
35
+ A('a#This is a link');
36
+ A('code#const x = 42;');
37
+ ```
38
+
39
+ Responsive Global Styles
40
+ ```typescript
41
+ A.insertGlobalCss({
42
+ "html": "font-size:16px",
43
+ "body": "line-height:1.6",
44
+ "h1, h2, h3": "font-weight:600 mt:$4 mb:$2",
45
+ "@media (max-width: 768px)": {
46
+ "html": "font-size:14px",
47
+ "body": "p:$2"
48
+ },
49
+ "@media (prefers-color-scheme: dark)": {
50
+ "body": "bg:#1a1a1a fg:#e5e5e5",
51
+ "code": "bg:#2a2a2a"
52
+ }
53
+ });
54
+ ```
55
+ At-rules such as `@media` and `@keyframes` should use nested objects. For keyframes,
56
+ the step selectors (`0%`, `50%`, `100%`, etc.) become the nested keys and each value
57
+ should be a concise CSS declaration string.
58
+
59
+ Animation Keyframes
60
+ ```typescript
61
+ A.insertGlobalCss({
62
+ "@keyframes connection-pulse": {
63
+ "0%": "box-shadow: inset 0 0 0 0 rgba(255, 70, 70, 0.4);",
64
+ "50%": "box-shadow: inset 0 0 0 6px rgba(255, 70, 70, 0.08);",
65
+ "100%": "box-shadow: inset 0 0 0 0 rgba(255, 70, 70, 0.4);"
66
+ }
67
+ });
68
+ ```
@@ -0,0 +1,19 @@
1
+ ## interceptLinks · function
2
+
3
+ Intercept clicks and Enter key presses on links (`<a>` tags) and use Aberdeen routing
4
+ instead of browser navigation for local paths (paths without a protocol or host).
5
+
6
+ This allows you to use regular HTML anchor tags for navigation without needing to
7
+ manually attach click handlers to each link.
8
+
9
+ **Signature:** `() => void`
10
+
11
+ **Examples:**
12
+
13
+ ```js
14
+ // In your root component:
15
+ route.interceptLinks();
16
+
17
+ // Now you can use regular anchor tags:
18
+ A('a text=About href=/corporate/about');
19
+ ```
@@ -0,0 +1,32 @@
1
+ ## invertString · function
2
+
3
+ Creates a new string that has the opposite sort order compared to the input string.
4
+
5
+ This is achieved by flipping the bits of each character code in the input string.
6
+ The resulting string is intended for use as a sort key, particularly with the
7
+ `makeKey` function in `onEach`, to achieve a descending sort order.
8
+
9
+ **Warning:** The output string will likely contain non-printable characters or
10
+ appear as gibberish and should not be displayed to the user.
11
+
12
+ **Signature:** `(input: string) => string`
13
+
14
+ **Parameters:**
15
+
16
+ - `input: string` - The string whose sort order needs to be inverted.
17
+
18
+ **Returns:** A new string that will sort in the reverse order of the input string.
19
+
20
+ **Examples:**
21
+
22
+ ```typescript
23
+ const $users = A.proxy([
24
+ { id: 1, name: 'Charlie', score: 95 },
25
+ { id: 2, name: 'Alice', score: 100 },
26
+ { id: 3, name: 'Bob', score: 90 },
27
+ ]);
28
+
29
+ A.onEach($users, ($user) => {
30
+ A(`p#${$user.name}: ${$user.score}`);
31
+ }, ($user) => A.invertString($user.name)); // Reverse alphabetic order
32
+ ```
@@ -0,0 +1,38 @@
1
+ ## isEmpty · function
2
+
3
+ Reactively checks if an observable array, object, Map, or Set is empty.
4
+
5
+ This function not only returns the current emptiness state but also establishes
6
+ a reactive dependency. If the emptiness state of the `proxied` object or array
7
+ changes later (e.g., an item is added to an empty array, or the last property
8
+ is deleted from an object), the scope that called `isEmpty` will be automatically
9
+ scheduled for re-evaluation.
10
+
11
+ **Signature:** `(proxied: TargetType) => boolean`
12
+
13
+ **Parameters:**
14
+
15
+ - `proxied: TargetType` - The observable array, object, Map, or Set to check.
16
+
17
+ **Returns:** `true` if the array has length 0, the Map/Set has size 0, or the object has no own enumerable properties, `false` otherwise.
18
+
19
+ **Examples:**
20
+
21
+ ```typescript
22
+ const $items = A.proxy([]);
23
+
24
+ // Reactively display a message if the items array is empty
25
+ A('div', () => {
26
+ if (A.isEmpty($items)) {
27
+ A('p i#No items yet!');
28
+ } else {
29
+ A.onEach($items, item => A('p#'+item));
30
+ }
31
+ });
32
+
33
+ // Adding an item will automatically remove the "No items yet!" message
34
+ setInterval(() => {
35
+ if (!$items.length || Math.random()>0.5) $items.push('Item');
36
+ else $items.length = 0;
37
+ }, 1000)
38
+ ```
package/skill/map.md ADDED
@@ -0,0 +1,16 @@
1
+ ## map · function
2
+
3
+ When using a Map as `source`.
4
+
5
+ **Signature:** `{ <K, IN, OUT>(source: Map<K, IN>, func: (value: IN, key: K) => OUT): Map<K, OUT>; <IN, OUT>(source: IN[], func: (value: IN, index: number) => OUT): OUT[]; <IN, const IN_KEY extends string | number | symbol, OUT>(source: Record<...>, func: (value: IN, index: KeyToString<...>) => OUT): Record<...>; }`
6
+
7
+ **Type Parameters:**
8
+
9
+ - `K`
10
+ - `IN`
11
+ - `OUT`
12
+
13
+ **Parameters:**
14
+
15
+ - `source: Map<K, IN>`
16
+ - `func: (value: IN, key: K) => undefined | OUT`
@@ -0,0 +1,27 @@
1
+ ## matchCurrent · function
2
+
3
+ Returns `true` if the current route matches `target`.
4
+
5
+ Path must match exactly. Any search params specified in `target` must be present
6
+ in the current URL, but extra params in the current URL are allowed.
7
+
8
+ Reactive: only reevaluates when the path changes to/from the target path, and
9
+ when target k/v search pairs are (un)set.
10
+
11
+ Primary usage: 'active' status for menu items.
12
+
13
+ **Signature:** `(target: RouteTarget) => boolean`
14
+
15
+ **Parameters:**
16
+
17
+ - `target: RouteTarget`
18
+
19
+ **Examples:**
20
+
21
+ ```js
22
+ // This example assumes interceptLinks() has been called
23
+ A('a.my-button text=Users href=/users .is-active=', route.matchCurrent('/users'));
24
+
25
+ // Alternatively a route object can be given
26
+ route.matchCurrent({path: '/users', search: {tab: 'profile'}});
27
+ ```