aberdeen 1.4.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aberdeen.d.ts +72 -7
- package/dist/aberdeen.js +52 -7
- package/dist/aberdeen.js.map +3 -3
- package/dist/route.js +4 -4
- package/dist/route.js.map +3 -3
- package/dist-min/aberdeen.js +7 -5
- package/dist-min/aberdeen.js.map +3 -3
- package/dist-min/route.js.map +2 -2
- package/package.json +5 -2
- package/skill/SKILL.md +579 -199
- package/skill/aberdeen.md +2322 -0
- package/skill/dispatcher.md +126 -0
- package/skill/prediction.md +73 -0
- package/skill/route.md +249 -0
- package/skill/transitions.md +59 -0
- package/src/aberdeen.ts +129 -15
- package/src/route.ts +3 -3
- package/skill/references/prediction.md +0 -45
- package/skill/references/routing.md +0 -81
- package/skill/references/transitions.md +0 -52
package/src/route.ts
CHANGED
|
@@ -170,9 +170,9 @@ export function go(target: RouteTarget, nav: NavType = "go"): void {
|
|
|
170
170
|
* @param target Same as for {@link go}, but merged into the current route instead deleting all state.
|
|
171
171
|
*/
|
|
172
172
|
export function push(target: RouteTarget): void {
|
|
173
|
-
|
|
174
|
-
merge(
|
|
175
|
-
go(
|
|
173
|
+
const c = clone(unproxy(current));
|
|
174
|
+
merge(c, targetToPartial(target));
|
|
175
|
+
go(c);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/**
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Prediction (Optimistic UI)
|
|
2
|
-
|
|
3
|
-
Apply UI changes immediately, auto-revert when server responds.
|
|
4
|
-
|
|
5
|
-
## API
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { applyPrediction, applyCanon } from 'aberdeen/prediction';
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
### `applyPrediction(func)`
|
|
12
|
-
Runs function and records all proxy changes as a "prediction".
|
|
13
|
-
Returns a `Patch` to use as `dropPatches` later, when the server responds.
|
|
14
|
-
|
|
15
|
-
### `applyCanon(func?, dropPatches?)`
|
|
16
|
-
1. Reverts all predictions
|
|
17
|
-
2. Runs `func` (typically applies server data)
|
|
18
|
-
3. Drops specified patches
|
|
19
|
-
4. Re-applies remaining predictions that still apply cleanly
|
|
20
|
-
|
|
21
|
-
## Example
|
|
22
|
-
```typescript
|
|
23
|
-
async function toggleTodo(todo: Todo) {
|
|
24
|
-
// Optimistic update
|
|
25
|
-
const patch = applyPrediction(() => {
|
|
26
|
-
todo.done = !todo.done;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const data = await api.updateTodo(todo.id, { done: todo.done });
|
|
31
|
-
|
|
32
|
-
// Server responded - apply canonical state
|
|
33
|
-
applyCanon(() => {
|
|
34
|
-
Object.assign(todo, data);
|
|
35
|
-
}, [patch]);
|
|
36
|
-
} catch {
|
|
37
|
-
// On error, just drop the prediction to revert
|
|
38
|
-
applyCanon(undefined, [patch]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## When to Use
|
|
44
|
-
- When you want immediate UI feedback for user actions for which a server is authoritative.
|
|
45
|
-
- As doing this manually for each such case is tedious, this should usually be integrated into the data updating/fetching layer.
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# Routing and Dispatching
|
|
2
|
-
|
|
3
|
-
## Router (`aberdeen/route`)
|
|
4
|
-
|
|
5
|
-
The `current` object is a reactive proxy of the current URL state.
|
|
6
|
-
|
|
7
|
-
### Properties
|
|
8
|
-
| Property | Type | Description |
|
|
9
|
-
|----------|------|-------------|
|
|
10
|
-
| `path` | `string` | Normalized path (e.g., `/users/123`) |
|
|
11
|
-
| `p` | `string[]` | Path segments (e.g., `['users', '123']`) |
|
|
12
|
-
| `search` | `Record<string,string>` | Query parameters |
|
|
13
|
-
| `hash` | `string` | URL hash including `#` |
|
|
14
|
-
| `state` | `Record<string,any>` | JSON-compatible state data |
|
|
15
|
-
| `nav` | `NavType` | How we got here: `load`, `back`, `forward`, `go`, `push` |
|
|
16
|
-
| `depth` | `number` | Navigation stack depth (starts at 1) |
|
|
17
|
-
|
|
18
|
-
### Navigation Functions
|
|
19
|
-
```typescript
|
|
20
|
-
import * as route from 'aberdeen/route';
|
|
21
|
-
|
|
22
|
-
route.go('/users/42'); // Navigate to new URL
|
|
23
|
-
route.go({ p: ['users', 42], hash: 'top' }); // Object form
|
|
24
|
-
route.push({ search: { tab: 'feed' } }); // Merge into current route
|
|
25
|
-
route.back(); // Go back in history
|
|
26
|
-
route.back({ path: '/home' }); // Back to matching entry, or replace
|
|
27
|
-
route.up(); // Go up one path level
|
|
28
|
-
route.persistScroll(); // Save/restore scroll position
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Reactive Routing Example
|
|
32
|
-
```typescript
|
|
33
|
-
import * as route from 'aberdeen/route';
|
|
34
|
-
|
|
35
|
-
$(() => {
|
|
36
|
-
const [section, id] = route.current.p;
|
|
37
|
-
if (section === 'users') drawUser(id);
|
|
38
|
-
else if (section === 'settings') drawSettings();
|
|
39
|
-
else drawHome();
|
|
40
|
-
});
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Dispatcher (`aberdeen/dispatcher`)
|
|
44
|
-
|
|
45
|
-
Type-safe path segment matching for complex routing.
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
import { Dispatcher, matchRest } from 'aberdeen/dispatcher';
|
|
49
|
-
import * as route from 'aberdeen/route';
|
|
50
|
-
|
|
51
|
-
const d = new Dispatcher();
|
|
52
|
-
|
|
53
|
-
// Literal string match
|
|
54
|
-
d.addRoute('home', () => drawHome());
|
|
55
|
-
|
|
56
|
-
// Number extraction (uses built-in Number function)
|
|
57
|
-
d.addRoute('user', Number, (id) => drawUser(id));
|
|
58
|
-
|
|
59
|
-
// String extraction
|
|
60
|
-
d.addRoute('user', Number, 'post', String, (userId, postId) => {
|
|
61
|
-
drawPost(userId, postId);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Rest of path as array
|
|
65
|
-
d.addRoute('search', matchRest, (terms: string[]) => {
|
|
66
|
-
performSearch(terms);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Dispatch in reactive scope
|
|
70
|
-
$(() => {
|
|
71
|
-
if (!d.dispatch(route.current.p)) {
|
|
72
|
-
draw404();
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Custom Matchers
|
|
78
|
-
```typescript
|
|
79
|
-
const uuid = (s: string) => /^[0-9a-f-]{36}$/.test(s) ? s : matchFailed;
|
|
80
|
-
d.addRoute('item', uuid, (id) => drawItem(id));
|
|
81
|
-
```
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# Transitions
|
|
2
|
-
|
|
3
|
-
Animate elements entering/leaving the DOM via the `create` and `destroy` properties.
|
|
4
|
-
|
|
5
|
-
**Important:** Transitions only trigger for **top-level** elements of a scope being (re-)run. Deeply nested elements drawn as part of a larger redraw do not trigger transitions.
|
|
6
|
-
|
|
7
|
-
## Built-in Transitions
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
import { grow, shrink } from 'aberdeen/transitions';
|
|
11
|
-
|
|
12
|
-
// Apply to individual elements
|
|
13
|
-
$('div create=', grow, 'destroy=', shrink, '#Animated');
|
|
14
|
-
|
|
15
|
-
// Common with onEach for list animations
|
|
16
|
-
onEach(items, item => {
|
|
17
|
-
$('li create=', grow, 'destroy=', shrink, `#${item.text}`);
|
|
18
|
-
});
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
- `grow`: Scales element from 0 to full size with margin animation
|
|
22
|
-
- `shrink`: Scales element to 0 and removes from DOM after animation
|
|
23
|
-
|
|
24
|
-
Both detect horizontal flex containers and animate width instead of height.
|
|
25
|
-
|
|
26
|
-
## CSS-Based Transitions
|
|
27
|
-
|
|
28
|
-
For custom transitions, use CSS class strings (dot-separated):
|
|
29
|
-
```typescript
|
|
30
|
-
const fadeStyle = insertCss({
|
|
31
|
-
transition: 'all 0.3s ease-out',
|
|
32
|
-
'&.hidden': { opacity: 0, transform: 'translateY(-10px)' }
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Class added briefly on create (removed after layout)
|
|
36
|
-
// Class added on destroy (element removed after 2s delay)
|
|
37
|
-
$('div', fadeStyle, 'create=.hidden destroy=.hidden#Fading element');
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Custom Transition Functions
|
|
41
|
-
|
|
42
|
-
For full control, pass functions. For `destroy`, your function must remove the element:
|
|
43
|
-
```typescript
|
|
44
|
-
$('div create=', (el: HTMLElement) => {
|
|
45
|
-
// Animate on mount - element already in DOM
|
|
46
|
-
el.animate([{ opacity: 0 }, { opacity: 1 }], 300);
|
|
47
|
-
}, 'destroy=', (el: HTMLElement) => {
|
|
48
|
-
// YOU must remove the element when done
|
|
49
|
-
el.animate([{ opacity: 1 }, { opacity: 0 }], 300)
|
|
50
|
-
.finished.then(() => el.remove());
|
|
51
|
-
}, '#Custom animated');
|
|
52
|
-
```
|