rask-ui 0.3.4 → 0.4.1
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/README.md +266 -96
- package/dist/batch.d.ts +7 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/batch.js +107 -0
- package/dist/component.d.ts +9 -1
- package/dist/component.d.ts.map +1 -1
- package/dist/component.js +42 -12
- package/dist/createComputed.d.ts +4 -0
- package/dist/createComputed.d.ts.map +1 -0
- package/dist/createComputed.js +41 -0
- package/dist/createEffect.d.ts +2 -0
- package/dist/createEffect.d.ts.map +1 -0
- package/dist/createEffect.js +25 -0
- package/dist/createState.d.ts +1 -0
- package/dist/createState.d.ts.map +1 -1
- package/dist/createState.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/observation.d.ts +82 -6
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +171 -21
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +195 -0
- package/dist/scheduler.d.ts +4 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +107 -0
- package/package.json +1 -1
- package/dist/createRef.d.ts +0 -5
- package/dist/createRef.d.ts.map +0 -1
- package/dist/createRef.js +0 -7
- package/dist/test-setup.d.ts +0 -16
- package/dist/test-setup.d.ts.map +0 -1
- package/dist/test-setup.js +0 -40
- package/dist/vdom/AbstractVNode.d.ts +0 -44
- package/dist/vdom/AbstractVNode.d.ts.map +0 -1
- package/dist/vdom/AbstractVNode.js +0 -256
- package/dist/vdom/ComponentVNode.d.ts +0 -48
- package/dist/vdom/ComponentVNode.d.ts.map +0 -1
- package/dist/vdom/ComponentVNode.js +0 -221
- package/dist/vdom/ElementVNode.d.ts +0 -27
- package/dist/vdom/ElementVNode.d.ts.map +0 -1
- package/dist/vdom/ElementVNode.js +0 -220
- package/dist/vdom/FragmentVNode.d.ts +0 -13
- package/dist/vdom/FragmentVNode.d.ts.map +0 -1
- package/dist/vdom/FragmentVNode.js +0 -49
- package/dist/vdom/RootVNode.d.ts +0 -25
- package/dist/vdom/RootVNode.d.ts.map +0 -1
- package/dist/vdom/RootVNode.js +0 -79
- package/dist/vdom/TextVNode.d.ts +0 -11
- package/dist/vdom/TextVNode.d.ts.map +0 -1
- package/dist/vdom/TextVNode.js +0 -35
- package/dist/vdom/dom-utils.d.ts +0 -14
- package/dist/vdom/dom-utils.d.ts.map +0 -1
- package/dist/vdom/dom-utils.js +0 -103
- package/dist/vdom/index.d.ts +0 -10
- package/dist/vdom/index.d.ts.map +0 -1
- package/dist/vdom/index.js +0 -26
- package/dist/vdom/types.d.ts +0 -20
- package/dist/vdom/types.d.ts.map +0 -1
- package/dist/vdom/types.js +0 -1
- package/dist/vdom/utils.d.ts +0 -6
- package/dist/vdom/utils.d.ts.map +0 -1
- package/dist/vdom/utils.js +0 -63
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
<img src="logo.png" alt="Logo" width="200">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
A lightweight reactive component library that combines the simplicity of observable state management with the full power of a virtual DOM reconciler.
|
|
7
|
+
A lightweight reactive component library that combines the simplicity of observable state management with the full power of a virtual DOM reconciler. Ideal for single page applications, using web technology.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install rask-ui
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
## The
|
|
13
|
+
## The Itch with Modern UI Frameworks
|
|
14
14
|
|
|
15
15
|
Modern UI frameworks present developers with a fundamental tradeoff between state management and UI expression:
|
|
16
16
|
|
|
@@ -42,9 +42,9 @@ function MyApp() {
|
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Solid offers a simpler mental model with fine-grained reactivity. Updates don't happen by calling the component function again,
|
|
45
|
+
Solid offers a seamingly simpler mental model with fine-grained reactivity. Updates don't happen by calling the component function again, which resolves the mental strain of expressing state management, however:
|
|
46
46
|
|
|
47
|
-
-
|
|
47
|
+
- The code you write is compiled to balance DX vs requirements of the runtime
|
|
48
48
|
- Special components for expressing dynamic UIs (`<Show>`, `<For>`, etc.)
|
|
49
49
|
- Different signatures for accessing reactive values: `count()` VS `state.count`
|
|
50
50
|
|
|
@@ -60,13 +60,11 @@ function MyApp() {
|
|
|
60
60
|
|
|
61
61
|
RASK gives you:
|
|
62
62
|
|
|
63
|
-
- **
|
|
64
|
-
- **Full reconciler power** - Express complex UIs naturally with
|
|
65
|
-
- **No
|
|
66
|
-
- **No compiler magic** - Plain JavaScript/TypeScript
|
|
67
|
-
- **Simple mental model** - State updates trigger only affected components
|
|
63
|
+
- **Simple state management** - No reconciler interference with your state management
|
|
64
|
+
- **Full reconciler power** - Express complex UIs naturally with the language
|
|
65
|
+
- **No compiler magic** - Plain JavaScript/TypeScript, it runs as you write it
|
|
68
66
|
|
|
69
|
-
Built on [Inferno JS](https://github.com/infernojs/inferno).
|
|
67
|
+
:fire: Built on [Inferno JS](https://github.com/infernojs/inferno).
|
|
70
68
|
|
|
71
69
|
## Getting Started
|
|
72
70
|
|
|
@@ -76,6 +74,19 @@ Built on [Inferno JS](https://github.com/infernojs/inferno).
|
|
|
76
74
|
npm install rask-ui
|
|
77
75
|
```
|
|
78
76
|
|
|
77
|
+
### Configuration
|
|
78
|
+
|
|
79
|
+
Configure TypeScript to use RASK's JSX runtime:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"compilerOptions": {
|
|
84
|
+
"jsx": "react-jsx",
|
|
85
|
+
"jsxImportSource": "rask-ui"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
79
90
|
### Basic Example
|
|
80
91
|
|
|
81
92
|
```tsx
|
|
@@ -182,9 +193,9 @@ function Parent() {
|
|
|
182
193
|
|
|
183
194
|
When `state.count` changes in Parent, only Child re-renders because it accesses `props.value`.
|
|
184
195
|
|
|
185
|
-
###
|
|
196
|
+
### One Rule To Accept
|
|
186
197
|
|
|
187
|
-
**RASK
|
|
198
|
+
**RASK has observable primitives**: Never destructure reactive objects (state, props, context values, async, query, mutation). Destructuring extracts plain values and breaks reactivity.
|
|
188
199
|
|
|
189
200
|
```tsx
|
|
190
201
|
// ❌ BAD - Destructuring breaks reactivity
|
|
@@ -234,6 +245,7 @@ Reactive objects are implemented using JavaScript Proxies. When you access a pro
|
|
|
234
245
|
- `createQuery()` - Never destructure query objects
|
|
235
246
|
- `createMutation()` - Never destructure mutation objects
|
|
236
247
|
- `createView()` - Never destructure view objects
|
|
248
|
+
- `createComputed()` - Never destructure computed objects
|
|
237
249
|
|
|
238
250
|
## API Reference
|
|
239
251
|
|
|
@@ -300,24 +312,26 @@ Creates a view that merges multiple objects (reactive or plain) into a single ob
|
|
|
300
312
|
```tsx
|
|
301
313
|
import { createView, createState } from "rask-ui";
|
|
302
314
|
|
|
303
|
-
function
|
|
315
|
+
function createCounter() {
|
|
304
316
|
const state = createState({ count: 0, name: "Counter" });
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
reset: () => (state.count = 0),
|
|
309
|
-
};
|
|
317
|
+
const increment = () => state.count++;
|
|
318
|
+
const decrement = () => state.count--;
|
|
319
|
+
const reset = () => (state.count = 0);
|
|
310
320
|
|
|
311
|
-
|
|
321
|
+
return createView(state, { increment, decrement, reset });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function Counter() {
|
|
325
|
+
const counter = createCounter();
|
|
312
326
|
|
|
313
327
|
return () => (
|
|
314
328
|
<div>
|
|
315
329
|
<h1>
|
|
316
|
-
{
|
|
330
|
+
{counter.name}: {counter.count}
|
|
317
331
|
</h1>
|
|
318
|
-
<button onClick={
|
|
319
|
-
<button onClick={
|
|
320
|
-
<button onClick={
|
|
332
|
+
<button onClick={counter.increment}>+</button>
|
|
333
|
+
<button onClick={counter.decrement}>-</button>
|
|
334
|
+
<button onClick={counter.reset}>Reset</button>
|
|
321
335
|
</div>
|
|
322
336
|
);
|
|
323
337
|
}
|
|
@@ -329,38 +343,6 @@ function Counter() {
|
|
|
329
343
|
|
|
330
344
|
**Returns:** A view object with getters for all properties, maintaining reactivity
|
|
331
345
|
|
|
332
|
-
**Use Cases:**
|
|
333
|
-
|
|
334
|
-
1. **Merge state with helper methods:**
|
|
335
|
-
|
|
336
|
-
```tsx
|
|
337
|
-
const state = createState({ count: 0 });
|
|
338
|
-
const helpers = { increment: () => state.count++ };
|
|
339
|
-
const view = createView(state, helpers);
|
|
340
|
-
// Access both: view.count, view.increment()
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
2. **Combine multiple reactive objects:**
|
|
344
|
-
|
|
345
|
-
```tsx
|
|
346
|
-
const user = createState({ name: "Alice" });
|
|
347
|
-
const settings = createState({ theme: "dark" });
|
|
348
|
-
const view = createView(user, settings);
|
|
349
|
-
// Access both: view.name, view.theme
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
3. **Override properties with computed values:**
|
|
353
|
-
```tsx
|
|
354
|
-
const state = createState({ firstName: "John", lastName: "Doe" });
|
|
355
|
-
const computed = {
|
|
356
|
-
get fullName() {
|
|
357
|
-
return `${state.firstName} ${state.lastName}`;
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
const view = createView(state, computed);
|
|
361
|
-
// view.fullName returns "John Doe" and updates when state changes
|
|
362
|
-
```
|
|
363
|
-
|
|
364
346
|
**Notes:**
|
|
365
347
|
|
|
366
348
|
- Reactivity is maintained through getters that reference the source objects
|
|
@@ -403,25 +385,171 @@ function Example() {
|
|
|
403
385
|
|
|
404
386
|
Pass the ref to an element's `ref` prop. The `current` property will be set to the DOM element when mounted and `null` when unmounted.
|
|
405
387
|
|
|
406
|
-
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
### Reactivity Primitives
|
|
391
|
+
|
|
392
|
+
#### `createEffect(callback)`
|
|
393
|
+
|
|
394
|
+
Creates an effect that automatically tracks reactive dependencies and re-runs whenever they change. The effect runs immediately on creation.
|
|
407
395
|
|
|
408
396
|
```tsx
|
|
409
|
-
|
|
410
|
-
const svgRef = createRef<SVGSVGElement>();
|
|
397
|
+
import { createEffect, createState } from "rask-ui";
|
|
411
398
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
399
|
+
function Timer() {
|
|
400
|
+
const state = createState({ count: 0, log: [] });
|
|
401
|
+
|
|
402
|
+
// Effect runs immediately and whenever state.count changes
|
|
403
|
+
createEffect(() => {
|
|
404
|
+
console.log("Count changed:", state.count);
|
|
405
|
+
state.log.push(`Count: ${state.count}`);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
return () => (
|
|
409
|
+
<div>
|
|
410
|
+
<p>Count: {state.count}</p>
|
|
411
|
+
<button onClick={() => state.count++}>Increment</button>
|
|
412
|
+
<ul>
|
|
413
|
+
{state.log.map((entry, i) => (
|
|
414
|
+
<li key={i}>{entry}</li>
|
|
415
|
+
))}
|
|
416
|
+
</ul>
|
|
417
|
+
</div>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Parameters:**
|
|
423
|
+
|
|
424
|
+
- `callback: () => void` - Function to run when dependencies change
|
|
425
|
+
|
|
426
|
+
**Features:**
|
|
427
|
+
|
|
428
|
+
- Runs immediately on creation
|
|
429
|
+
- Automatically tracks reactive dependencies accessed during execution
|
|
430
|
+
- Re-runs on microtask when dependencies change (prevents synchronous cascades)
|
|
431
|
+
- Automatically cleaned up when component unmounts
|
|
432
|
+
- Can be used for side effects like logging, syncing to localStorage, or updating derived state
|
|
433
|
+
|
|
434
|
+
**Notes:**
|
|
435
|
+
|
|
436
|
+
- Only call during component setup phase (not in render function)
|
|
437
|
+
- Effects are queued on microtask to avoid synchronous execution from prop changes
|
|
438
|
+
- Be careful with effects that modify state - can cause infinite loops if not careful
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
#### `createComputed<T>(computed)`
|
|
443
|
+
|
|
444
|
+
Creates an object with computed properties that automatically track dependencies and cache results until dependencies change.
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
import { createComputed, createState } from "rask-ui";
|
|
448
|
+
|
|
449
|
+
function ShoppingCart() {
|
|
450
|
+
const state = createState({
|
|
451
|
+
items: [
|
|
452
|
+
{ id: 1, name: "Apple", price: 1.5, quantity: 3 },
|
|
453
|
+
{ id: 2, name: "Banana", price: 0.8, quantity: 5 },
|
|
454
|
+
],
|
|
455
|
+
taxRate: 0.2,
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const computed = createComputed({
|
|
459
|
+
subtotal: () =>
|
|
460
|
+
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
|
|
461
|
+
tax: () => computed.subtotal * state.taxRate,
|
|
462
|
+
total: () => computed.subtotal + computed.tax,
|
|
463
|
+
itemCount: () => state.items.reduce((sum, item) => sum + item.quantity, 0),
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
return () => (
|
|
467
|
+
<div>
|
|
468
|
+
<h2>Cart ({computed.itemCount} items)</h2>
|
|
469
|
+
<ul>
|
|
470
|
+
{state.items.map((item) => (
|
|
471
|
+
<li key={item.id}>
|
|
472
|
+
{item.name}: ${item.price} x {item.quantity}
|
|
473
|
+
<button onClick={() => item.quantity++}>+</button>
|
|
474
|
+
<button onClick={() => item.quantity--}>-</button>
|
|
475
|
+
</li>
|
|
476
|
+
))}
|
|
477
|
+
</ul>
|
|
478
|
+
<div>
|
|
479
|
+
<p>Subtotal: ${computed.subtotal.toFixed(2)}</p>
|
|
480
|
+
<p>
|
|
481
|
+
Tax ({state.taxRate * 100}%): ${computed.tax.toFixed(2)}
|
|
482
|
+
</p>
|
|
483
|
+
<p>
|
|
484
|
+
<strong>Total: ${computed.total.toFixed(2)}</strong>
|
|
485
|
+
</p>
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**Parameters:**
|
|
493
|
+
|
|
494
|
+
- `computed: T` - Object where each property is a function returning a computed value
|
|
495
|
+
|
|
496
|
+
**Returns:** Reactive object with cached computed properties
|
|
497
|
+
|
|
498
|
+
**Features:**
|
|
499
|
+
|
|
500
|
+
- **Lazy evaluation** - Computed values are only calculated when accessed
|
|
501
|
+
- **Automatic caching** - Results are cached until dependencies change
|
|
502
|
+
- **Dependency tracking** - Automatically tracks what state each computed depends on
|
|
503
|
+
- **Composable** - Computed properties can depend on other computed properties
|
|
504
|
+
- **Efficient** - Only recomputes when dirty (dependencies changed)
|
|
505
|
+
- **Automatic cleanup** - Cleaned up when component unmounts
|
|
506
|
+
|
|
507
|
+
**Notes:**
|
|
508
|
+
|
|
509
|
+
- Access computed properties directly (e.g., `computed.total`), don't call as functions
|
|
510
|
+
- Computed properties are getters, not functions
|
|
511
|
+
- **Do not destructure** - Breaks reactivity (see warning section above)
|
|
512
|
+
- Only call during component setup phase
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
### Automatic Batching
|
|
517
|
+
|
|
518
|
+
RASK automatically batches state updates to minimize re-renders. This happens transparently without any special syntax.
|
|
519
|
+
|
|
520
|
+
**How it works:**
|
|
521
|
+
|
|
522
|
+
- **User interactions** (clicks, inputs, keyboard, etc.) - State changes are batched and flushed synchronously at the end of the event
|
|
523
|
+
- **Other updates** (setTimeout, fetch callbacks, etc.) - State changes are batched and flushed on the next microtask
|
|
524
|
+
|
|
525
|
+
```tsx
|
|
526
|
+
function BatchingExample() {
|
|
527
|
+
const state = createState({ count: 0, clicks: 0 });
|
|
528
|
+
|
|
529
|
+
const handleClick = () => {
|
|
530
|
+
// All three updates are batched into a single render
|
|
531
|
+
state.count++;
|
|
532
|
+
state.clicks++;
|
|
533
|
+
state.count++;
|
|
534
|
+
// UI updates once with count=2, clicks=1
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const handleAsync = () => {
|
|
538
|
+
setTimeout(() => {
|
|
539
|
+
// These updates are also batched (async batch)
|
|
540
|
+
state.count++;
|
|
541
|
+
state.clicks++;
|
|
542
|
+
// UI updates once on next microtask
|
|
543
|
+
}, 100);
|
|
417
544
|
};
|
|
418
545
|
|
|
419
546
|
return () => (
|
|
420
547
|
<div>
|
|
421
|
-
<
|
|
422
|
-
|
|
423
|
-
</
|
|
424
|
-
<button onClick={
|
|
548
|
+
<p>
|
|
549
|
+
Count: {state.count}, Clicks: {state.clicks}
|
|
550
|
+
</p>
|
|
551
|
+
<button onClick={handleClick}>Sync Update</button>
|
|
552
|
+
<button onClick={handleAsync}>Async Update</button>
|
|
425
553
|
</div>
|
|
426
554
|
);
|
|
427
555
|
}
|
|
@@ -543,13 +671,16 @@ import { createAsync } from "rask-ui";
|
|
|
543
671
|
function UserProfile() {
|
|
544
672
|
const user = createAsync(fetch("/api/user").then((r) => r.json()));
|
|
545
673
|
|
|
546
|
-
return () =>
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
674
|
+
return () => }
|
|
675
|
+
if (user.isPending) {
|
|
676
|
+
return <p>Loading...</p>
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (user.error) {
|
|
680
|
+
return <p>Error: {user.error}</p>
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return <p>Hello, {user.value.name}!</p>
|
|
553
684
|
}
|
|
554
685
|
```
|
|
555
686
|
|
|
@@ -580,16 +711,25 @@ import { createQuery } from "rask-ui";
|
|
|
580
711
|
|
|
581
712
|
function Posts() {
|
|
582
713
|
const posts = createQuery(() => fetch("/api/posts").then((r) => r.json()));
|
|
714
|
+
const renderPosts = () => {
|
|
715
|
+
if (posts.isPending) {
|
|
716
|
+
return <p>Loading...</p>;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (posts.error) {
|
|
720
|
+
return <p>Error: {posts.error}</p>;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return posts.data.map((post) => (
|
|
724
|
+
<article key={post.id}>{post.title}</article>
|
|
725
|
+
));
|
|
726
|
+
};
|
|
583
727
|
|
|
584
728
|
return () => (
|
|
585
729
|
<div>
|
|
586
730
|
<button onClick={() => posts.fetch()}>Refresh</button>
|
|
587
731
|
<button onClick={() => posts.fetch(true)}>Force Refresh</button>
|
|
588
|
-
|
|
589
|
-
{posts.isPending && <p>Loading...</p>}
|
|
590
|
-
{posts.error && <p>Error: {posts.error}</p>}
|
|
591
|
-
{posts.data &&
|
|
592
|
-
posts.data.map((post) => <article key={post.id}>{post.title}</article>)}
|
|
732
|
+
{renderPosts()}
|
|
593
733
|
</div>
|
|
594
734
|
);
|
|
595
735
|
}
|
|
@@ -630,7 +770,7 @@ function CreatePost() {
|
|
|
630
770
|
fetch("/api/posts", {
|
|
631
771
|
method: "POST",
|
|
632
772
|
body: JSON.stringify(data),
|
|
633
|
-
})
|
|
773
|
+
}))
|
|
634
774
|
);
|
|
635
775
|
|
|
636
776
|
const handleSubmit = () => {
|
|
@@ -768,7 +908,9 @@ Keys prevent component recreation when list order changes.
|
|
|
768
908
|
|
|
769
909
|
### Computed Values
|
|
770
910
|
|
|
771
|
-
|
|
911
|
+
You can create computed values in two ways:
|
|
912
|
+
|
|
913
|
+
**1. Simple computed functions** - For basic derived values:
|
|
772
914
|
|
|
773
915
|
```tsx
|
|
774
916
|
function ShoppingCart() {
|
|
@@ -799,6 +941,49 @@ function ShoppingCart() {
|
|
|
799
941
|
|
|
800
942
|
Computed functions automatically track dependencies when called during render.
|
|
801
943
|
|
|
944
|
+
**2. Using `createComputed`** - For cached, efficient computed values with automatic dependency tracking:
|
|
945
|
+
|
|
946
|
+
```tsx
|
|
947
|
+
function ShoppingCart() {
|
|
948
|
+
const state = createState({
|
|
949
|
+
items: [
|
|
950
|
+
{ id: 1, price: 10, quantity: 2 },
|
|
951
|
+
{ id: 2, price: 20, quantity: 1 },
|
|
952
|
+
],
|
|
953
|
+
taxRate: 0.1,
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
const computed = createComputed({
|
|
957
|
+
subtotal: () =>
|
|
958
|
+
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
|
|
959
|
+
tax: () => computed.subtotal * state.taxRate,
|
|
960
|
+
total: () => computed.subtotal + computed.tax,
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
return () => (
|
|
964
|
+
<div>
|
|
965
|
+
<ul>
|
|
966
|
+
{state.items.map((item) => (
|
|
967
|
+
<li key={item.id}>
|
|
968
|
+
${item.price} x {item.quantity}
|
|
969
|
+
</li>
|
|
970
|
+
))}
|
|
971
|
+
</ul>
|
|
972
|
+
<p>Subtotal: ${computed.subtotal}</p>
|
|
973
|
+
<p>Tax: ${computed.tax}</p>
|
|
974
|
+
<p>Total: ${computed.total}</p>
|
|
975
|
+
</div>
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
Benefits of `createComputed`:
|
|
981
|
+
|
|
982
|
+
- **Cached** - Only recalculates when dependencies change
|
|
983
|
+
- **Lazy** - Only calculates when accessed
|
|
984
|
+
- **Composable** - Computed properties can depend on other computed properties
|
|
985
|
+
- **Efficient** - Better performance for expensive calculations
|
|
986
|
+
|
|
802
987
|
### Composition
|
|
803
988
|
|
|
804
989
|
Compose complex logic by combining state and methods using `createView`:
|
|
@@ -945,21 +1130,6 @@ function TodoList() {
|
|
|
945
1130
|
}
|
|
946
1131
|
```
|
|
947
1132
|
|
|
948
|
-
## Configuration
|
|
949
|
-
|
|
950
|
-
### JSX Setup
|
|
951
|
-
|
|
952
|
-
Configure TypeScript to use RASK's JSX runtime:
|
|
953
|
-
|
|
954
|
-
```json
|
|
955
|
-
{
|
|
956
|
-
"compilerOptions": {
|
|
957
|
-
"jsx": "react-jsx",
|
|
958
|
-
"jsxImportSource": "rask-ui"
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
```
|
|
962
|
-
|
|
963
1133
|
## Performance
|
|
964
1134
|
|
|
965
1135
|
RASK is designed for performance:
|
package/dist/batch.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type QueuedCallback = (() => void) & {
|
|
2
|
+
__queued: boolean;
|
|
3
|
+
};
|
|
4
|
+
export declare function queue(cb: QueuedCallback): void;
|
|
5
|
+
export declare function syncBatch(cb: () => void): void;
|
|
6
|
+
export declare function installEventBatching(target?: EventTarget): void;
|
|
7
|
+
//# sourceMappingURL=batch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC;AAwClE,wBAAgB,KAAK,CAAC,EAAE,EAAE,cAAc,QAYvC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,QAavC;AAED,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,WAAsB,QA4BlE"}
|
package/dist/batch.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const INTERACTIVE_EVENTS = [
|
|
2
|
+
// DISCRETE
|
|
3
|
+
"beforeinput",
|
|
4
|
+
"input",
|
|
5
|
+
"change",
|
|
6
|
+
"compositionend",
|
|
7
|
+
"keydown",
|
|
8
|
+
"keyup",
|
|
9
|
+
"click",
|
|
10
|
+
"contextmenu",
|
|
11
|
+
"submit",
|
|
12
|
+
"reset",
|
|
13
|
+
// GESTURE START
|
|
14
|
+
"pointerdown",
|
|
15
|
+
"mousedown",
|
|
16
|
+
"touchstart",
|
|
17
|
+
// GESTURE END
|
|
18
|
+
"pointerup",
|
|
19
|
+
"mouseup",
|
|
20
|
+
"touchend",
|
|
21
|
+
"touchcancel",
|
|
22
|
+
];
|
|
23
|
+
const asyncQueue = [];
|
|
24
|
+
const syncQueue = [];
|
|
25
|
+
let inInteractive = 0;
|
|
26
|
+
let asyncScheduled = false;
|
|
27
|
+
let inSyncBatch = 0;
|
|
28
|
+
function scheduleAsyncFlush() {
|
|
29
|
+
if (asyncScheduled)
|
|
30
|
+
return;
|
|
31
|
+
asyncScheduled = true;
|
|
32
|
+
queueMicrotask(flushAsyncQueue);
|
|
33
|
+
}
|
|
34
|
+
function flushAsyncQueue() {
|
|
35
|
+
asyncScheduled = false;
|
|
36
|
+
if (!asyncQueue.length)
|
|
37
|
+
return;
|
|
38
|
+
for (let i = 0; i < asyncQueue.length; i++) {
|
|
39
|
+
const cb = asyncQueue[i];
|
|
40
|
+
asyncQueue[i] = undefined;
|
|
41
|
+
cb();
|
|
42
|
+
cb.__queued = false;
|
|
43
|
+
}
|
|
44
|
+
asyncQueue.length = 0;
|
|
45
|
+
}
|
|
46
|
+
function flushSyncQueue() {
|
|
47
|
+
if (!syncQueue.length)
|
|
48
|
+
return;
|
|
49
|
+
for (let i = 0; i < syncQueue.length; i++) {
|
|
50
|
+
const cb = syncQueue[i];
|
|
51
|
+
syncQueue[i] = undefined;
|
|
52
|
+
cb();
|
|
53
|
+
cb.__queued = false;
|
|
54
|
+
}
|
|
55
|
+
syncQueue.length = 0;
|
|
56
|
+
}
|
|
57
|
+
export function queue(cb) {
|
|
58
|
+
cb.__queued = true;
|
|
59
|
+
if (inSyncBatch) {
|
|
60
|
+
syncQueue.push(cb);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
asyncQueue.push(cb);
|
|
64
|
+
if (!inInteractive) {
|
|
65
|
+
scheduleAsyncFlush();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function syncBatch(cb) {
|
|
69
|
+
inSyncBatch++;
|
|
70
|
+
try {
|
|
71
|
+
cb();
|
|
72
|
+
// Only flush on successful completion
|
|
73
|
+
inSyncBatch--;
|
|
74
|
+
if (!inSyncBatch) {
|
|
75
|
+
flushSyncQueue();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
inSyncBatch--;
|
|
80
|
+
throw e; // Re-throw without flushing
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function installEventBatching(target = document) {
|
|
84
|
+
const captureOptions = {
|
|
85
|
+
capture: true,
|
|
86
|
+
passive: true,
|
|
87
|
+
};
|
|
88
|
+
const bubbleOptions = { passive: true };
|
|
89
|
+
const onCapture = () => {
|
|
90
|
+
inInteractive++;
|
|
91
|
+
scheduleAsyncFlush(); // backup in case of stopPropagation
|
|
92
|
+
};
|
|
93
|
+
const onBubble = () => {
|
|
94
|
+
if (--inInteractive === 0 && asyncQueue.length) {
|
|
95
|
+
// Flush inline once outermost interactive event finishes
|
|
96
|
+
flushAsyncQueue();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
INTERACTIVE_EVENTS.forEach((type) => {
|
|
100
|
+
target.addEventListener(type, onCapture, captureOptions);
|
|
101
|
+
});
|
|
102
|
+
queueMicrotask(() => {
|
|
103
|
+
INTERACTIVE_EVENTS.forEach((type) => {
|
|
104
|
+
target.addEventListener(type, onBubble, bubbleOptions);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
package/dist/component.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ declare class RaskComponent<P extends Props<any>> extends Component<P & {
|
|
|
10
10
|
private reactiveProps?;
|
|
11
11
|
private observer;
|
|
12
12
|
private isRendering;
|
|
13
|
+
effects: Array<{
|
|
14
|
+
isDirty: boolean;
|
|
15
|
+
run: () => void;
|
|
16
|
+
}>;
|
|
13
17
|
contexts: Map<any, any>;
|
|
14
18
|
getChildContext(): any;
|
|
15
19
|
onMounts: Array<() => void>;
|
|
@@ -17,7 +21,11 @@ declare class RaskComponent<P extends Props<any>> extends Component<P & {
|
|
|
17
21
|
private createReactiveProps;
|
|
18
22
|
componentDidMount(): void;
|
|
19
23
|
componentWillUnmount(): void;
|
|
20
|
-
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
*/
|
|
27
|
+
componentWillUpdate(nextProps: any): void;
|
|
28
|
+
componentWillReceiveProps(): void;
|
|
21
29
|
shouldComponentUpdate(nextProps: Props<any>): boolean;
|
|
22
30
|
render(): any;
|
|
23
31
|
}
|
package/dist/component.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,SAAS,EACT,KAAK,EAEN,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,SAAS,EACT,KAAK,EAEN,MAAM,SAAS,CAAC;AAQjB,wBAAgB,mBAAmB,uBAMlC;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,QAMrC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,QAMvC;AAED,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAClD,CAAC,MAAM,MAAM,KAAK,CAAC,GACnB,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,KAAK,CAAC,CAAC;AAEhC,cAAM,aAAa,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,CAAE,SAAQ,SAAS,CACzD,CAAC,GAAG;IAAE,WAAW,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAA;CAAE,CAC9C;IACC,OAAO,CAAC,QAAQ,CAAC,CAAc;IAC/B,OAAO,CAAC,aAAa,CAAC,CAAa;IACnC,OAAO,CAAC,QAAQ,CAEb;IACH,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAM;IAC3D,QAAQ,gBAAa;IACrB,eAAe;IAUf,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IACjC,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IACnC,OAAO,CAAC,mBAAmB;IA4D3B,iBAAiB,IAAI,IAAI;IAGzB,oBAAoB,IAAI,IAAI;IAG5B;;OAEG;IACH,mBAAmB,CAAC,SAAS,EAAE,GAAG;IAYlC,yBAAyB,IAAI,IAAI;IACjC,qBAAqB,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO;IAerD,MAAM;CAyCP;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,SAO9D"}
|