aberdeen 0.4.0 → 0.5.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/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  ISC License
2
2
 
3
- Copyright (c) 2021-2024 Frank van Viegen
3
+ Copyright (c) 2021-2025 Frank van Viegen
4
4
 
5
5
  Permission to use, copy, modify, and/or distribute this software for any
6
6
  purpose with or without fee is hereby granted, provided that the above
package/README.md CHANGED
@@ -1,130 +1,169 @@
1
1
  A TypeScript/JavaScript library for quickly building performant declarative user interfaces *without* the use of a virtual DOM.
2
2
 
3
- The key insight is the use of many small anonymous functions, that will automatically rerun when the underlying data changes. In order to trigger updates, that data should be encapsulated in any number of `Store` objects. They can hold anything, from simple values to deeply nested data structures, in which case user-interface functions can (automatically) subscribe to just the parts they depend upon.
4
-
3
+ The key insight is the use of many small anonymous functions, that will automatically rerun when the underlying data changes. In order to trigger updates, that data should be encapsulated in any number of *proxied* JavaScript objects. They can hold anything, from simple values to complex and deeply nested data structures, in which case user-interface functions can (automatically) subscribe to just the parts they depend upon.
5
4
 
6
5
  ## Why use Aberdeen?
7
6
 
8
7
  - It provides a flexible and simple to understand model for reactive user-interface building.
9
8
  - It allows you to express user-interfaces in plain JavaScript (or TypeScript) in an easy to read form, without (JSX-like) compilation steps.
10
- - It's fast, as it doesn't use a *virtual DOM* and only reruns small pieces of code in response to updated data. It also makes displaying and updating sorted lists very easy and very fast.
11
- - It's lightweight, at about 16kb minimized and without any run-time dependencies.
12
- - It comes with batteries included, providing modules for..
9
+ - It's fast, as it doesn't use a *virtual DOM* and only reruns small pieces of code, redrawing minimal pieces of the UI, in response to updated data.
10
+ - It makes displaying and updating sorted lists very easy and very fast.
11
+ - It's tiny, at about 5kb (minimized and gzipped) and without any run-time dependencies.
12
+ - It comes with batteries included:
13
13
  - Client-side routing.
14
- - Optimistic user-interface updates (predictions) while awaiting a server response.
15
- - Transitions.
14
+ - Revertible patches, for optimistic user-interface updates.
15
+ - Component-local CSS generator.
16
+ - Helper functions for reactively working with data, such as for deriving, (multi)mapping, filtering, partitioning and counting.
17
+ - A couple of add/remove transition effects, to get you started.
16
18
 
17
- ## Examples
19
+ ## Why *not* use Aberdeen?
18
20
 
19
- - [Tic-tac-toe demo](https://vanviegen.github.io/aberdeen/examples/tic-tac-toe/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/tic-tac-toe)
20
- - [Input example demo](https://vanviegen.github.io/aberdeen/examples/input/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/input)
21
- - [List example demo](https://vanviegen.github.io/aberdeen/examples/list/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/list)
22
- - [Routing example demo](https://vanviegen.github.io/aberdeen/examples/router/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/router)
21
+ - There are not many of us -Aberdeen developers- yet, so don't expect terribly helpful StackOver/AI answers.
22
+ - You'd have to code things yourself, instead of duct-taping together a gazillion React ecosystem libraries.
23
23
 
24
+ ## Example code
24
25
 
25
- To get a quick impression of what Aberdeen code looks like, this is all of the JavaScript for the above Tic-tac-toe demo:
26
+ To get a quick impression of what Aberdeen code looks like, below is a Tic-tac-toe app with undo history. If you're reading this on [the official website](https://vanviegen.github.io/aberdeen/README/) you should see a working demo below the code, and an 'edit' button in the top-right corner of the code, to play around.
26
27
 
27
28
  ```javascript
28
- import {$, mount, Store} from 'https://cdn.jsdelivr.net/npm/aberdeen/+esm';
29
+ import {$, proxy, onEach, insertCss, observe} from "aberdeen";
29
30
 
30
- // Observable data
31
- const squares = new Store([]) // eg. ['X', undefined, 'O', 'X']
32
- const history = new Store([[]]) // eg. [[], [undefined, undefined, undefined, X], ...]
33
- const historyPos = new Store(null) // set while 'time traveling' our undo history
31
+ // Helper functions
34
32
 
35
- // Derived data
36
- const winner = squares.derive(calculateWinner) // 'X', 'O' or undefined
37
- const player = squares.derive(sq => sq.filter(v => v).length % 2 ? "O" : "X") // 'X' or 'O'
33
+ function calculateWinner(board) {
34
+ const lines = [
35
+ [0, 1, 2], [3, 4, 5], [6, 7, 8], // horizontal
36
+ [0, 3, 6], [1, 4, 7], [2, 5, 8], // vertical
37
+ [0, 4, 8], [2, 4, 6] // diagonal
38
+ ];
39
+ for (const [a, b, c] of lines) {
40
+ if (board[a] && board[a] === board[b] && board[a] === board[c]) {
41
+ return board[a];
42
+ }
43
+ }
44
+ }
38
45
 
39
- // Rendering functions
46
+ function getCurrentMarker(board) {
47
+ return board.filter(v => v).length % 2 ? "O" : "X";
48
+ }
40
49
 
41
- function drawSquare(position) {
42
- $('button.square', () => {
43
- let value = squares(position).get()
44
- if (value) $({text: value})
45
- else $({click: () => fillSquare(position)})
46
- })
50
+ function getBoard(history) {
51
+ return history.boards[history.current];
47
52
  }
48
53
 
49
- function drawBoard() {
50
- for(let y=0; y<3; y++) {
51
- $('div.board-row', () => {
52
- for(let x=0; x<3; x++) {
53
- drawSquare(y*3 + x)
54
- }
55
- })
56
- }
54
+ function markSquare(history, position) {
55
+ const board = getBoard(history);
56
+
57
+ // Don't allow markers when we already have a winner
58
+ if (calculateWinner(board)) return;
59
+
60
+ // Copy the current board, and insert the marker into it
61
+ const newBoard = board.slice();
62
+ newBoard[position] = getCurrentMarker(board);
63
+
64
+ // Truncate any future states, and write a new future
65
+ history.current++;
66
+ history.boards.length = history.current;
67
+ history.boards.push(newBoard);
57
68
  }
58
69
 
59
- function drawInfo() {
60
- $('div', () => {
61
- if (winner.get()) {
62
- $({text: `Winner: ${winner.get()}!`})
63
- } else {
64
- $({text: `Current player: ${player.get()}`})
65
- }
66
- })
67
- $('.buttons', () => {
68
- history.onEach(item => {
69
- $('button', {
70
- text: item.index() ? `Go to move ${item.index()}` : `Go to game start`,
71
- click: () => {
72
- historyPos.set(item.index())
73
- squares.set(item.get())
74
- },
75
- })
76
- })
77
- })
70
+ // Define component-local CSS, which we'll utilize in the drawBoard function.
71
+ // Of course, you can use any other styling solution instead, if you prefer.
72
+
73
+ const boardStyle = insertCss({
74
+ display: 'grid',
75
+ gap: '0.5em',
76
+ gridTemplateColumns: '1fr 1fr 1fr',
77
+ '> *': {
78
+ width: '2em',
79
+ height: '2em',
80
+ padding: 0,
81
+ },
82
+ });
83
+
84
+ // UI drawing functions.
85
+
86
+ function drawBoard(history) {
87
+ $('div', boardStyle, () => {
88
+ for(let pos=0; pos<9; pos++) {
89
+ $('button.square', () => {
90
+ let marker = getBoard(history)[pos];
91
+ if (marker) {
92
+ $({ text: marker });
93
+ } else {
94
+ $({ click: () => markSquare(history, pos) });
95
+ }
96
+ });
97
+ }
98
+ })
78
99
  }
79
100
 
80
- // Helper functions
101
+ function drawStatusMessage(history) {
102
+ $('h4', () => {
103
+ // Reruns whenever observable data read by calculateWinner or getCurrentMarker changes
104
+ const board = getBoard(history);
105
+ const winner = calculateWinner(board);
106
+ if (winner) {
107
+ $(`:Winner: ${winner}!`);
108
+ } else if (board.filter(square=>square).length === 9) {
109
+ $(`:It's a draw...`);
110
+ } else {
111
+ $(`:Current player: ${getCurrentMarker(board)}`);
112
+ }
113
+ });
114
+ }
81
115
 
82
- function fillSquare(position) {
83
- // If there's already a winner, don't allow a new square to be filled
84
- if (winner.peek()) return
85
-
86
- // Fill the square
87
- squares(position).set(player.get())
88
-
89
- if (historyPos.get() != null) {
90
- // Truncate everything after history pos
91
- history.modify(h => h.slice(0, historyPos.get()+1))
92
- // Stop 'time traveling'
93
- historyPos.set(null)
94
- }
95
-
96
- // Append the current squares-state to the history array
97
- history.push(squares.get())
116
+ function drawTurns(history) {
117
+ $('div:Select a turn:')
118
+ // Reactively iterate all (historic) board versions
119
+ onEach(history.boards, (_, index) => {
120
+ $('button', {
121
+ // A text node:
122
+ text: index,
123
+ // Conditional css class:
124
+ ".outline": observe(() => history.current != index),
125
+ // Inline styles:
126
+ $marginRight: "0.5em",
127
+ $marginTop: "0.5em",
128
+ // Event listener:
129
+ click: () => history.current = index,
130
+ });
131
+ });
98
132
  }
99
133
 
100
- function calculateWinner(squares) {
101
- const lines = [
102
- [0, 1, 2], [3, 4, 5], [6, 7, 8], // horizontal
103
- [0, 3, 6], [1, 4, 7], [2, 5, 8], // vertical
104
- [0, 4, 8], [2, 4, 6] // diagonal
105
- ];
106
- for (const [a, b, c] of lines) {
107
- if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
108
- return squares[a];
109
- }
110
- }
134
+ function drawMain() {
135
+ // Define our state, wrapped by an observable proxy
136
+ const history = proxy({
137
+ boards: [[]], // eg. [[], [undefined, 'O', undefined, 'X'], ...]
138
+ current: 0, // indicates which of the boards is currently showing
139
+ });
140
+
141
+ $('main.row', () => {
142
+ $('div.box', () => drawBoard(history));
143
+ $('div.box', {$flex: 1}, () => {
144
+ drawStatusMessage(history);
145
+ drawTurns(history);
146
+ });
147
+ });
111
148
  }
112
149
 
113
- // Fire it up!
114
-
115
- mount(document.body, () => {
116
- $('.game', () => {
117
- $('.game-board', drawBoard)
118
- $('.game-info', drawInfo)
119
- })
120
- })
150
+ // Fire it up! Mounts on document.body by default..
151
+
152
+ drawMain();
121
153
  ```
122
154
 
155
+ Some further examples:
156
+
157
+ - [Input example demo](https://vanviegen.github.io/aberdeen/examples/input/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/input)
158
+ - [List example demo](https://vanviegen.github.io/aberdeen/examples/list/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/list)
159
+ - [Routing example demo](https://vanviegen.github.io/aberdeen/examples/router/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/router)
160
+ - [JS Framework Benchmark demo](https://vanviegen.github.io/aberdeen/examples/js-framework-benchmark/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/js-framework-benchmark)
123
161
 
124
- ## Reference documentation
125
162
 
126
- https://vanviegen.github.io/aberdeen/modules.html
163
+ ## Documentation
127
164
 
165
+ - [Tutorial](https://vanviegen.github.io/aberdeen/Tutorial/)
166
+ - [Reference documentation](https://vanviegen.github.io/aberdeen/modules.html)
128
167
 
129
168
  ## Roadmap
130
169
 
@@ -132,8 +171,7 @@ https://vanviegen.github.io/aberdeen/modules.html
132
171
  - [x] A better alternative for scheduleTask.
133
172
  - [x] A simple router.
134
173
  - [x] Optimistic client-side predictions.
135
- - [ ] Support for (component local) CSS or possibly a tailwind-like abstraction.
136
- - [ ] More user friendly documentation generator.
174
+ - [x] Performance profiling and tuning regarding lists.
175
+ - [x] Support for (component local) CSS
137
176
  - [ ] Architecture document.
138
177
  - [ ] SVG support.
139
- - [ ] Performance profiling and tuning regarding lists.