|
8 | 8 | <img src="https://img.shields.io/npm/v/round-core?color=brightgreen" alt="NPM Version" /> |
9 | 9 | </p> |
10 | 10 |
|
11 | | -<p align="center"> |
12 | | - <em>Round is a lightweight frontend framework focused on building <b>single-page applications (SPAs)</b> with <b>fine‑grained reactivity.</b></em> |
13 | | -</p> |
| 11 | +<h3 align="center"> |
| 12 | + <em><b>Round</b> is a lightweight, DOM-first framework for building SPAs with fine-grained reactivity, and fast, predictable updates powered by signals and bindables</em> |
| 13 | +</h3> |
| 14 | + |
14 | 15 |
|
15 | 16 | <div align="center"> |
16 | 17 |
|
@@ -74,7 +75,7 @@ Round includes a CLI with a project initializer. |
74 | 75 |
|
75 | 76 | ```bash |
76 | 77 | # Install the CLI |
77 | | -npm install round |
| 78 | +bun add round-core |
78 | 79 |
|
79 | 80 | # Create a new app |
80 | 81 | round init myapp |
@@ -142,6 +143,15 @@ export default function Counter() { |
142 | 143 | } |
143 | 144 | ``` |
144 | 145 |
|
| 146 | +### Signals Internals |
| 147 | + |
| 148 | +RoundJS utilizes a high-performance reactivity engine designed for efficiency and minimal memory overhead: |
| 149 | + |
| 150 | +- **Doubly-Linked List Dependency Tracking**: Instead of using heavy `Set` objects, RoundJS uses a linked-list of subscription nodes. This eliminates array spreads and object allocations during signal updates, providing constant-time performance for adding/removing dependencies. |
| 151 | +- **Global Versioning (Clock)**: Every signal write increments a global version counter. Computed signals (`derive`) track the version of their dependencies and only recompute if they are "dirty" (out of date). This ensures true lazyness and avoids redundant calculations. |
| 152 | +- **Automatic Batching**: Multiple signal updates within the same execution cycle are batched. Effects and DOM updates only trigger once at the end of the batch, preventing "glitches" and unnecessary re-renders. |
| 153 | + |
| 154 | + |
145 | 155 | ### `derive(fn)` |
146 | 156 |
|
147 | 157 | Create a computed signal that updates automatically when its dependencies change. |
@@ -242,6 +252,40 @@ export function Example() { |
242 | 252 | } |
243 | 253 | ``` |
244 | 254 |
|
| 255 | +## DOM binding directives |
| 256 | + |
| 257 | +Round supports two-way bindings via props: |
| 258 | + |
| 259 | +- `bind:value={someBindable}` for text-like inputs, `<textarea>`, and `<select>`. |
| 260 | +- `bind:checked={someBindable}` for `<input type="checkbox">` and `<input type="radio">`. |
| 261 | + |
| 262 | +Round will warn if the value is not signal-like, and will warn if you bind a plain `signal()` instead of a `bindable()`. |
| 263 | + |
| 264 | +### `bindable.object(initialObject)` and deep binding |
| 265 | + |
| 266 | +Round supports object-shaped state with ergonomic deep bindings via proxies. |
| 267 | + |
| 268 | +```jsx |
| 269 | +import { bindable } from 'round-core'; |
| 270 | + |
| 271 | +export function Profile() { |
| 272 | + const user = bindable.object({ |
| 273 | + profile: { bio: '' }, |
| 274 | + flags: { newsletter: false } |
| 275 | + }); |
| 276 | + |
| 277 | + return ( |
| 278 | + <div> |
| 279 | + <textarea bind:value={user.profile.bio} /> |
| 280 | + <label> |
| 281 | + <input type="checkbox" bind:checked={user.flags.newsletter} /> |
| 282 | + Subscribe |
| 283 | + </label> |
| 284 | + </div> |
| 285 | + ); |
| 286 | +} |
| 287 | +``` |
| 288 | + |
245 | 289 | ### `createStore(initialState, actions)` |
246 | 290 |
|
247 | 291 | Create a shared global state store with actions and optional persistence. |
@@ -286,26 +330,31 @@ const data = store.snapshot({ reactive: false }); // Get static JSON of state |
286 | 330 | store.set('todos', []); // Direct set |
287 | 331 | ``` |
288 | 332 |
|
289 | | -### `bindable.object(initialObject)` and deep binding |
| 333 | +### `.validate(validator, options)` |
290 | 334 |
|
291 | | -Round supports object-shaped state with ergonomic deep bindings via proxies. |
| 335 | +Attach validation to a signal/bindable. |
| 336 | + |
| 337 | +- Invalid writes do not update the underlying value. |
| 338 | +- `signal.error` is itself a signal (reactive) containing the current error message or `null`. |
| 339 | +- `options.validateOn` can be `'input'` (default) or `'blur'`. |
| 340 | +- `options.validateInitial` can trigger validation on startup. |
292 | 341 |
|
293 | 342 | ```jsx |
294 | 343 | import { bindable } from 'round-core'; |
295 | 344 |
|
296 | | -export function Profile() { |
297 | | - const user = bindable.object({ |
298 | | - profile: { bio: '' }, |
299 | | - flags: { newsletter: false } |
300 | | - }); |
| 345 | +export function EmailField() { |
| 346 | + const email = bindable('') |
| 347 | + .validate( |
| 348 | + (v) => v.includes('@') || 'Invalid email', |
| 349 | + { validateOn: 'blur' } |
| 350 | + ); |
301 | 351 |
|
302 | 352 | return ( |
303 | 353 | <div> |
304 | | - <textarea bind:value={user.profile.bio} /> |
305 | | - <label> |
306 | | - <input type="checkbox" bind:checked={user.flags.newsletter} /> |
307 | | - Subscribe |
308 | | - </label> |
| 354 | + <input bind:value={email} placeholder="name@example.com" /> |
| 355 | + <div style={() => ({ color: email.error() ? 'crimson' : '#666' })}> |
| 356 | + {email.error} |
| 357 | + </div> |
309 | 358 | </div> |
310 | 359 | ); |
311 | 360 | } |
@@ -343,45 +392,6 @@ export function MyComponent() { |
343 | 392 | } |
344 | 393 | ``` |
345 | 394 |
|
346 | | -### `.validate(validator, options)` |
347 | | - |
348 | | -Attach validation to a signal/bindable. |
349 | | - |
350 | | -- Invalid writes do not update the underlying value. |
351 | | -- `signal.error` is itself a signal (reactive) containing the current error message or `null`. |
352 | | -- `options.validateOn` can be `'input'` (default) or `'blur'`. |
353 | | -- `options.validateInitial` can trigger validation on startup. |
354 | | - |
355 | | -```jsx |
356 | | -import { bindable } from 'round-core'; |
357 | | - |
358 | | -export function EmailField() { |
359 | | - const email = bindable('') |
360 | | - .validate( |
361 | | - (v) => v.includes('@') || 'Invalid email', |
362 | | - { validateOn: 'blur' } |
363 | | - ); |
364 | | - |
365 | | - return ( |
366 | | - <div> |
367 | | - <input bind:value={email} placeholder="name@example.com" /> |
368 | | - <div style={() => ({ color: email.error() ? 'crimson' : '#666' })}> |
369 | | - {email.error} |
370 | | - </div> |
371 | | - </div> |
372 | | - ); |
373 | | -} |
374 | | -``` |
375 | | - |
376 | | -## DOM binding directives |
377 | | - |
378 | | -Round supports two-way bindings via props: |
379 | | - |
380 | | -- `bind:value={someBindable}` for text-like inputs, `<textarea>`, and `<select>`. |
381 | | -- `bind:checked={someBindable}` for `<input type="checkbox">` and `<input type="radio">`. |
382 | | - |
383 | | -Round will warn if the value is not signal-like, and will warn if you bind a plain `signal()` instead of a `bindable()`. |
384 | | - |
385 | 395 | ## JSX superset control flow |
386 | 396 |
|
387 | 397 | Round extends JSX inside `.round` files with a control-flow syntax that compiles to JavaScript. |
@@ -418,7 +428,7 @@ This compiles to efficient **keyed reconciliation** using the `ForKeyed` runtime |
418 | 428 | - **Keyed (Recommended)**: By providing `key=expr`, Round maintains the identity of DOM nodes. If the list reorders, Round moves the existing nodes instead of recreating them. This preserves local state (like input focus, cursor position, or CSS animations). |
419 | 429 | - **Unkeyed**: If no key is provided, Round simply maps over the list. Reordering the list will cause nodes to be reused based on their index, which might lead to state issues in complex lists. |
420 | 430 |
|
421 | | -### `switch` |
| 431 | +### `switch(...)` |
422 | 432 |
|
423 | 433 | ```jsx |
424 | 434 | {switch(status()){ |
@@ -538,14 +548,6 @@ The CLI is intended for day-to-day development: |
538 | 548 |
|
539 | 549 | Run `round -h` to see available commands. |
540 | 550 |
|
541 | | -## Signals Internals |
542 | | - |
543 | | -RoundJS utilizes a high-performance reactivity engine designed for efficiency and minimal memory overhead: |
544 | | - |
545 | | -- **Doubly-Linked List Dependency Tracking**: Instead of using heavy `Set` objects, RoundJS uses a linked-list of subscription nodes. This eliminates array spreads and object allocations during signal updates, providing constant-time performance for adding/removing dependencies. |
546 | | -- **Global Versioning (Clock)**: Every signal write increments a global version counter. Computed signals (`derive`) track the version of their dependencies and only recompute if they are "dirty" (out of date). This ensures true lazyness and avoids redundant calculations. |
547 | | -- **Automatic Batching**: Multiple signal updates within the same execution cycle are batched. Effects and DOM updates only trigger once at the end of the batch, preventing "glitches" and unnecessary re-renders. |
548 | | - |
549 | 551 | ## Performance |
550 | 552 |
|
551 | 553 | RoundJS sits in a powerful "middle ground" of performance: |
|
0 commit comments