aberdeen 1.3.1 → 1.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aberdeen",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "author": "Frank van Viegen",
5
5
  "main": "dist-min/aberdeen.js",
6
6
  "devDependencies": {
package/src/aberdeen.ts CHANGED
@@ -189,6 +189,19 @@ abstract class Scope implements QueueRunner {
189
189
  // }
190
190
  }
191
191
 
192
+ /**
193
+ * Execute a function once, after all currently scheduled jobs are completed.
194
+ */
195
+ class DelayedOneTimeRunner implements QueueRunner {
196
+ prio: number = --lastPrio;
197
+ [ptr: ReverseSortedSetPointer]: this;
198
+ constructor(
199
+ public queueRun: () => void
200
+ ) {
201
+ queue(this);
202
+ }
203
+ }
204
+
192
205
  /**
193
206
  * All Scopes that can hold nodes and subscopes, including `SimpleScope` and `OnEachItemScope`
194
207
  * but *not* `OnEachScope`, are `ContentScope`s.
@@ -1806,8 +1819,10 @@ function applyBind(el: HTMLInputElement, target: any) {
1806
1819
  onProxyChange = () => {
1807
1820
  el.value = target.value;
1808
1821
  // biome-ignore lint/suspicious/noDoubleEquals: it's fine for numbers to be casts to strings here
1809
- if (el.tagName === "SELECT" && el.value != target.value)
1810
- throw new Error(`SELECT has no '${target.value}' OPTION (yet)`);
1822
+ if (el.tagName === "SELECT" && el.value != target.value) {
1823
+ // Presumable, OPTIONs haven't been created yet. Try again after all currently queued work has been done.
1824
+ new DelayedOneTimeRunner(() => el.value = target.value);
1825
+ }
1811
1826
  };
1812
1827
  }
1813
1828
  derive(onProxyChange);
package/README.md.bak DELETED
@@ -1,212 +0,0 @@
1
- # [Aberdeen](https://aberdeenjs.org/) [![](https://img.shields.io/badge/license-ISC-blue.svg)](https://github.com/vanviegen/aberdeen/blob/master/LICENSE.txt) [![](https://badge.fury.io/js/aberdeen.svg)](https://badge.fury.io/js/aberdeen) ![](https://img.shields.io/bundlejs/size/aberdeen) [![](https://img.shields.io/github/last-commit/vanviegen/aberdeen)](https://github.com/vanviegen/aberdeen)
2
-
3
- Build fast reactive UIs in pure TypeScript/JavaScript without a virtual DOM.
4
-
5
- Aberdeen's approach is refreshingly simple:
6
-
7
- > Use many small anonymous functions for emitting DOM elements, and automatically rerun them when their underlying data changes. JavaScript `Proxy` is used to track reads and updates to this data, which can consist of anything, from simple values to complex, typed, and deeply nested data structures.
8
-
9
- ## Why use Aberdeen?
10
-
11
- - 🎩 **Simple:** Express UIs naturally in JavaScript/TypeScript, without build steps or JSX, and with a minimal amount of concepts you need to learn.
12
- - ⏩ **Fast:** No virtual DOM. Aberdeen intelligently updates only the minimal, necessary parts of your UI when proxied data changes.
13
- - 👥 **Awesome lists**: It's very easy and performant to reactively display data sorted by whatever you like.
14
- - 🔬 **Tiny:** Around 6KB (minimized and gzipped) for the core system. Zero runtime dependencies.
15
- - 🔋 **Batteries included**: Comes with browser history management, routing, revertible patches for optimistic user-interface updates, component-local CSS, SVG support, helper functions for transforming reactive data (mapping, partitioning, filtering, etc) and hide/unhide transition effects. No bikeshedding required!
16
-
17
- ## Why *not* use Aberdeen?
18
-
19
- - 🤷 **Lack of community:** There are not many of us -Aberdeen developers- yet, so don't expect terribly helpful Stack Overflow/AI answers.
20
- - 📚 **Lack of ecosystem:** You'd have to code things yourself, instead of duct-taping together a gazillion React ecosystem libraries.
21
-
22
- ## Examples
23
-
24
- First, let's start with the obligatory reactive counter example. If you're reading this on [the official website](https://aberdeenjs.org) you should see a working demo below the code, and an 'edit' button in the top-right corner of the code, to play around.
25
-
26
- ```javascript
27
- import A from 'aberdeen';
28
-
29
- // Define some state as a proxied (observable) object
30
- const state = A.proxy({question: "How many roads must a man walk down?", answer: 42});
31
-
32
- A`h3`(() => {
33
- // This function reruns whenever the question or the answer changes
34
- A`"${state.question} ↪ ${state.answer || 'Blowing in the wind'}"`;
35
- });
36
-
37
- // Two-way bind state.question to an <input>
38
- A`input placeholder=Question bind!${A.at(state, 'question')}`;
39
-
40
- // Allow state.answer to be modified using both an <input> and buttons
41
- A`div.row`(() => {
42
- A`button click=${() => state.answer--} "-"`;
43
- A`input type=number bind!${A.at(state, 'answer')}`;
44
- A`button click=${() => state.answer++} "+"`;
45
- });
46
- ```
47
-
48
- Okay, next up is a somewhat more complex app - a todo-list with the following behavior:
49
-
50
- - New items open in an 'editing state'.
51
- - Items that are in 'editing state' show a text input, a save button and a cancel button. Done status cannot be toggled while editing.
52
- - Pressing one of the buttons, or pressing enter will transition from 'editing state' to 'viewing state', saving the new label text unless cancel was pressed.
53
- - In 'viewing state', the label is shown as non-editable. There's an 'Edit' link, that will transition the item to 'editing state'. Clicking anywhere else will toggle the done status.
54
- - The list of items is sorted alphabetically by label. Items move when 'save' changes their label.
55
- - Items that are created, moved or deleted grow and shrink as appropriate.
56
-
57
- Pfew.. now let's look at the code:
58
-
59
- ```typescript
60
- import A from "aberdeen";
61
- import {grow, shrink} from "aberdeen/transitions";
62
-
63
- // We'll use a simple class to store our data.
64
- class TodoItem {
65
- constructor(public label: string = '', public done: boolean = false) {}
66
- toggle() { this.done = !this.done; }
67
- }
68
-
69
- // The top-level user interface.
70
- function drawMain() {
71
- // Add some initial items. We'll wrap a proxy() around it!
72
- let items: TodoItem[] = A.proxy([
73
- new TodoItem('Make todo-list demo', true),
74
- new TodoItem('Learn Aberdeen', false),
75
- ]);
76
-
77
- // Draw the list, ordered by label.
78
- A.onEach(items, drawItem, item => item.label);
79
-
80
- // Add item and delete checked buttons.
81
- A`div.row`(() => {
82
- A`button click=${() => items.push(new TodoItem(""))} "+"`;
83
- A`button.outline click=${() => {
84
- for(let idx in items) {
85
- if (items[idx].done) delete items[idx];
86
- }
87
- }} "Delete checked"`;
88
- });
89
- };
90
-
91
- // Called for each todo list item.
92
- function drawItem(item) {
93
- // Items without a label open in editing state.
94
- // Note that we're creating this proxy outside the `div.row` scope
95
- // create below, so that it will persist when that state reruns.
96
- let editing: {value: boolean} = A.proxy(item.label == '');
97
-
98
- A`div.row.${todoItemStyle} create!${grow} destroy!${shrink}`(() => {
99
- // Conditionally add a class to `div.row`, based on item.done
100
- A`if!${A.at(item,'done')} .done`;
101
-
102
- // The checkmark is hidden using CSS
103
- A`div.checkmark "✅"`;
104
-
105
- if (editing.value) {
106
- // Label <input>. Save using enter or button.
107
- function save() {
108
- editing.value = false;
109
- item.label = inputElement.value;
110
- }
111
- let inputElement = A`input placeholder=Label value~${item.label} keydown=${e => e.key==='Enter' && save()}`;
112
- A`button.outline click=${() => editing.value = false} "Cancel"`;
113
- A`button click=${save} "Save"`;
114
- } else {
115
- // Label as text.
116
- A`p "${item.label}"`;
117
-
118
- // Edit icon, if not done.
119
- if (!item.done) {
120
- A`a click=${e => {
121
- editing.value = true;
122
- e.stopPropagation(); // We don't want to toggle as well.
123
- }} "Edit"`;
124
- }
125
-
126
- // Clicking a row toggles done.
127
- A`click=${() => item.done = !item.done} cursor:pointer`;
128
- }
129
- });
130
- }
131
-
132
- // Insert some component-local CSS, specific for this demo.
133
- const todoItemStyle = A.insertCss({
134
- marginBottom: "0.5rem",
135
- ".checkmark": {
136
- opacity: 0.2,
137
- },
138
- "&.done": {
139
- textDecoration: "line-through",
140
- ".checkmark": {
141
- opacity: 1,
142
- },
143
- },
144
- });
145
-
146
- // Go!
147
- drawMain();
148
- ```
149
-
150
- Some further examples:
151
-
152
- - [Input demo](https://aberdeenjs.org/examples/input/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/input)
153
- - [Tic Tac Toe demo](https://aberdeenjs.org/examples/tictactoe/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/tictactoe)
154
- - [List demo](https://aberdeenjs.org/examples/list/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/list)
155
- - [Routing demo](https://aberdeenjs.org/examples/router/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/router)
156
- - [JS Framework Benchmark demo](https://aberdeenjs.org/examples/js-framework-benchmark/) - [Source](https://github.com/vanviegen/aberdeen/tree/master/examples/js-framework-benchmark)
157
-
158
- ## Learning Aberdeen
159
-
160
- - [Tutorial](https://aberdeenjs.org/Tutorial/)
161
- - [Reference documentation](https://aberdeenjs.org/modules.html)
162
-
163
- And you may want to study the examples above, of course!
164
-
165
- ## Changelog
166
-
167
- ### 1.2.0 (2025-09-27)
168
-
169
- **Enhancements:**
170
- - The `A` function now supports a concise template literal syntax for creating elements, setting attributes and properties, adding classes, etc. See the documentation for details.
171
- - The `A.proxy()` function can now accept `Promise`s, which will return an observable object with properties for `busy` status, `error` (if any), and the resolved `value`. This makes it easier to call async functions from within UI code.
172
-
173
- **Breaking changes:**
174
- - When a UI render function returns a `Promise`, that will now be reported as an error. Async render functions are fundamentally incompatible with Aberdeen's reactive model, so it's helpful to point that out. Use the new `A.proxy()` async support instead.
175
- - Setting attributes versus properties is no longer automatically inferred. Use `name=value` for attributes and `name~value` for properties.
176
- - Special attributes (like `bind`, `create`, `destroy`, `if`, `else`, `end`, and `html`) now use a different syntax: `name!value` or just `name!`.
177
- - Conditional rendering has been completely redesigned, now using `if!`, `else!`, and `end!` special attributes with different semantics.
178
-
179
- ### 1.1.0 (2025-09-12)
180
-
181
- This major release aims to reduce surprises in our API, aligning more closely with regular JavaScript semantics (for better or worse).
182
-
183
- **Breaking changes:**
184
-
185
- - Functions that iterate objects (like `A.onEach` and `A.map`) will now only work on *own* properties of the object, ignoring those in the prototype chain. The new behavior should be more consistent and faster.
186
- - These iteration function now properly distinguish between `undefined` and *empty*. Previously, object/array/map items with `undefined` values were considered non-existent. The new behavior (though arguably confusing) is more consistent with regular JavaScript semantics.
187
- - The `A.copy` function no longer ..
188
- - Supports `SHALLOW` and `MERGE` flags. The latter has been replaced by a dedicated `A.merge` function. The former turned out not to be particularly useful.
189
- - Has weird special cases that would allow copying objects into maps and merging objects into arrays.
190
- - Copies properties from the prototype chain of objects. Only *own* properties are copied now. As the prototype link itself *is* copied over, this should actually result in copies being *more* similar to the original.
191
- - The `observe` function has been renamed to `A.derive` to better reflect its purpose and match terminology used in other reactive programming libraries.
192
- - The `A.route` API brings some significant changes. Modifying the `A.route` observable (which should now be accessed as `A.route.current`) will now always result in changing the current browser history item (URL and state, using `replaceState`), instead of using a heuristic to figure out what you probably want. Dedicated functions have been added for navigating to a new URL (`A.go`), back to a previous URL (`A.back`), and for going up in the route hierarchy (`A.up`).
193
- - The concept of immediate observers (through the `immediateObserve` function) no longer exists. It caused unexpected behavior (for instance due to the fact that an array `pop()` in JavaScript is implemented as a delete followed by a length change, so happens in two steps that would each call immediate observers). The reason it existed was mostly to enable a pre-1.0 version of the `A.route` API. It turned out to be a mistake.
194
-
195
- **Enhancements:**
196
-
197
- - The `A.peek` function can now also accept an object and a key as argument (e.g. `A.peek(obj, 'myKey')`). It does the same as `A.peek(() => obj.myKey)`, but more concise and faster.
198
- - The `A.copy` and `A.merge` functions now ..
199
- - Accept an optional `dstKey` argument, allowing you to assign to a specific key with `A.copy` semantics, and without subscribing to the key.
200
- - Return a boolean indicating whether any changes were made.
201
- - Are faster.
202
- - A new `A.dispatcher` module has been added. It provides a simple and type-safe way to match URL paths to handler functions, and extract parameters from the path. You can still use your own routing solution if you prefer, of course.
203
- - The `A.route` module now also has tests, making the whole project now fully covered by tests.
204
-
205
- **Fixes:**
206
-
207
- - Browser-back behavior in the `A.route` module had some reliability issues after page reloads.
208
- - The `A.copy` and `A.clone` function created Maps and Arrays with the wrong internal type. So `instanceof Array` would say yes, while `Array.isArray` would say no. JavaScript is weird.
209
-
210
- ### 1.0.0 (2025-05-07)
211
-
212
- After five years of working on this library on and off, I'm finally happy with its API and the developer experience it offers. I'm calling it 1.0! To celebrate, I've created some pretty fancy (if I may say so myself) interactive documentation and a tutorial.