rask-ui 0.10.4 → 0.11.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/README.md +37 -2
- package/dist/createEffect.d.ts +1 -1
- package/dist/createEffect.d.ts.map +1 -1
- package/dist/createEffect.js +8 -1
- package/dist/createView.d.ts +21 -43
- package/dist/createView.d.ts.map +1 -1
- package/dist/createView.js +7 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -484,9 +484,41 @@ function Timer() {
|
|
|
484
484
|
}
|
|
485
485
|
```
|
|
486
486
|
|
|
487
|
+
**Effect with Disposal:**
|
|
488
|
+
|
|
489
|
+
The callback can optionally return a dispose function that runs before the effect executes again:
|
|
490
|
+
|
|
491
|
+
```tsx
|
|
492
|
+
import { createEffect, createState } from "rask-ui";
|
|
493
|
+
|
|
494
|
+
function LiveData() {
|
|
495
|
+
const state = createState({ url: "/api/data", data: null });
|
|
496
|
+
|
|
497
|
+
createEffect(() => {
|
|
498
|
+
const eventSource = new EventSource(state.url);
|
|
499
|
+
|
|
500
|
+
eventSource.onmessage = (event) => {
|
|
501
|
+
state.data = JSON.parse(event.data);
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// Dispose function runs before effect re-executes
|
|
505
|
+
return () => {
|
|
506
|
+
eventSource.close();
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
return () => (
|
|
511
|
+
<div>
|
|
512
|
+
<input value={state.url} onInput={(e) => state.url = e.target.value} />
|
|
513
|
+
<pre>{JSON.stringify(state.data, null, 2)}</pre>
|
|
514
|
+
</div>
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
487
519
|
**Parameters:**
|
|
488
520
|
|
|
489
|
-
- `callback: () => void` - Function to run when dependencies change
|
|
521
|
+
- `callback: () => void | (() => void)` - Function to run when dependencies change. Can optionally return a dispose function that runs before the effect executes again.
|
|
490
522
|
|
|
491
523
|
**Features:**
|
|
492
524
|
|
|
@@ -494,13 +526,16 @@ function Timer() {
|
|
|
494
526
|
- Automatically tracks reactive dependencies accessed during execution
|
|
495
527
|
- Re-runs on microtask when dependencies change (prevents synchronous cascades)
|
|
496
528
|
- Automatically cleaned up when component unmounts
|
|
497
|
-
-
|
|
529
|
+
- Optional dispose function for cleaning up resources before re-execution
|
|
530
|
+
- Can be used for side effects like logging, syncing to localStorage, managing subscriptions, or updating derived state
|
|
498
531
|
|
|
499
532
|
**Notes:**
|
|
500
533
|
|
|
501
534
|
- Only call during component setup phase (not in render function)
|
|
502
535
|
- Effects are queued on microtask to avoid synchronous execution from prop changes
|
|
503
536
|
- Be careful with effects that modify state - can cause infinite loops if not careful
|
|
537
|
+
- Dispose functions run before the effect re-executes, not when the component unmounts
|
|
538
|
+
- For component unmount cleanup, use `createCleanup()` instead
|
|
504
539
|
|
|
505
540
|
---
|
|
506
541
|
|
package/dist/createEffect.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function createEffect(cb: () => void): void;
|
|
1
|
+
export declare function createEffect(cb: () => void | (() => void)): void;
|
|
2
2
|
//# sourceMappingURL=createEffect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createEffect.d.ts","sourceRoot":"","sources":["../src/createEffect.ts"],"names":[],"mappings":"AAGA,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"createEffect.d.ts","sourceRoot":"","sources":["../src/createEffect.ts"],"names":[],"mappings":"AAGA,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,QA6BzD"}
|
package/dist/createEffect.js
CHANGED
|
@@ -8,14 +8,21 @@ export function createEffect(cb) {
|
|
|
8
8
|
catch {
|
|
9
9
|
currentComponent = undefined;
|
|
10
10
|
}
|
|
11
|
+
let disposer;
|
|
11
12
|
const observer = new Observer(() => {
|
|
12
13
|
// We trigger effects on micro task as synchronous observer notifications
|
|
13
14
|
// (Like when components sets props) should not synchronously trigger effects
|
|
14
15
|
queueMicrotask(runEffect);
|
|
15
16
|
});
|
|
16
17
|
const runEffect = () => {
|
|
18
|
+
try {
|
|
19
|
+
disposer?.();
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error("Error in effect dispose function:", error);
|
|
23
|
+
}
|
|
17
24
|
const stopObserving = observer.observe();
|
|
18
|
-
cb();
|
|
25
|
+
disposer = cb();
|
|
19
26
|
stopObserving();
|
|
20
27
|
};
|
|
21
28
|
if (currentComponent) {
|
package/dist/createView.d.ts
CHANGED
|
@@ -1,54 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Utility type that preserves unions while "flattening" object types.
|
|
3
|
+
*/
|
|
4
|
+
type Simplify<T> = T extends any ? {
|
|
2
5
|
[K in keyof T]: T[K];
|
|
3
|
-
}
|
|
4
|
-
|
|
6
|
+
} : never;
|
|
7
|
+
/**
|
|
8
|
+
* Merge two object types where properties from B override properties from A.
|
|
9
|
+
* Distributes over unions in A.
|
|
10
|
+
*/
|
|
11
|
+
type MergeTwo<A extends object, B extends object> = A extends any ? Simplify<Omit<A, keyof B> & B> : never;
|
|
12
|
+
/**
|
|
13
|
+
* Merge a readonly tuple of objects left-to-right, with later entries
|
|
14
|
+
* overriding earlier ones. Distributes over unions in the head element.
|
|
15
|
+
*/
|
|
5
16
|
type MergeMany<T extends readonly object[]> = T extends [
|
|
6
17
|
infer H extends object,
|
|
7
18
|
...infer R extends object[]
|
|
8
19
|
] ? MergeTwo<H, MergeMany<R>> : {};
|
|
9
20
|
/**
|
|
10
|
-
* Creates a view that merges multiple objects (reactive or not) into a single
|
|
11
|
-
* maintaining reactivity through getters. Properties from later
|
|
12
|
-
*
|
|
13
|
-
* @warning **Do not destructure the returned view object!** Destructuring breaks reactivity
|
|
14
|
-
* because it extracts plain values instead of maintaining getter access. This is the same rule
|
|
15
|
-
* as other reactive primitives.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* // ❌ Bad - destructuring loses reactivity
|
|
19
|
-
* function Component() {
|
|
20
|
-
* const state = createState({ count: 0 });
|
|
21
|
-
* const helpers = { increment: () => state.count++ };
|
|
22
|
-
* const view = createView(state, helpers);
|
|
23
|
-
* const { count, increment } = view; // Don't do this!
|
|
24
|
-
* return () => <button onClick={increment}>{count}</button>; // Won't update!
|
|
25
|
-
* }
|
|
26
|
-
*
|
|
27
|
-
* // ✅ Good - access properties directly in render
|
|
28
|
-
* function Component() {
|
|
29
|
-
* const state = createState({ count: 0 });
|
|
30
|
-
* const helpers = { increment: () => state.count++ };
|
|
31
|
-
* const view = createView(state, helpers);
|
|
32
|
-
* return () => <button onClick={view.increment}>{view.count}</button>; // Reactive!
|
|
33
|
-
* }
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* // Merge multiple reactive objects
|
|
37
|
-
* const state = createState({ count: 0 });
|
|
38
|
-
* const user = createState({ name: "Alice" });
|
|
39
|
-
* const view = createView(state, user);
|
|
40
|
-
* // view has both count and name properties, maintaining reactivity
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* // Later arguments override earlier ones
|
|
44
|
-
* const a = { x: 1, y: 2 };
|
|
45
|
-
* const b = { y: 3, z: 4 };
|
|
46
|
-
* const view = createView(a, b);
|
|
47
|
-
* // view.x === 1, view.y === 3, view.z === 4
|
|
21
|
+
* Creates a view that merges multiple objects (reactive or not) into a single
|
|
22
|
+
* object while maintaining reactivity through getters. Properties from later
|
|
23
|
+
* arguments override earlier ones.
|
|
48
24
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
25
|
+
* ⚠️ Do not destructure the returned view object; always read properties
|
|
26
|
+
* directly from the view to preserve reactivity.
|
|
51
27
|
*/
|
|
28
|
+
export declare function createView<A extends object>(a: A): A;
|
|
29
|
+
export declare function createView<A extends object, B extends object>(a: A, b: B): MergeTwo<A, B>;
|
|
52
30
|
export declare function createView<T extends readonly object[]>(...args: T): MergeMany<T>;
|
|
53
31
|
export {};
|
|
54
32
|
//# sourceMappingURL=createView.d.ts.map
|
package/dist/createView.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createView.d.ts","sourceRoot":"","sources":["../src/createView.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createView.d.ts","sourceRoot":"","sources":["../src/createView.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,KAAK,CAAC;AAEpE;;;GAGG;AACH,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,GAC7D,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAC9B,KAAK,CAAC;AAEV;;;GAGG;AACH,KAAK,SAAS,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,IAAI,CAAC,SAAS;IACtD,MAAM,CAAC,SAAS,MAAM;IACtB,GAAG,MAAM,CAAC,SAAS,MAAM,EAAE;CAC5B,GACG,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,GACzB,EAAE,CAAC;AAEP;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;AACtD,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAC3D,CAAC,EAAE,CAAC,EACJ,CAAC,EAAE,CAAC,GACH,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAClB,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,EACpD,GAAG,IAAI,EAAE,CAAC,GACT,SAAS,CAAC,CAAC,CAAC,CAAC"}
|
package/dist/createView.js
CHANGED
|
@@ -1,64 +1,22 @@
|
|
|
1
1
|
import { INSPECT_MARKER, INSPECTOR_ENABLED } from "./inspect";
|
|
2
|
-
/**
|
|
3
|
-
* Creates a view that merges multiple objects (reactive or not) into a single object while
|
|
4
|
-
* maintaining reactivity through getters. Properties from later arguments override earlier ones.
|
|
5
|
-
*
|
|
6
|
-
* @warning **Do not destructure the returned view object!** Destructuring breaks reactivity
|
|
7
|
-
* because it extracts plain values instead of maintaining getter access. This is the same rule
|
|
8
|
-
* as other reactive primitives.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* // ❌ Bad - destructuring loses reactivity
|
|
12
|
-
* function Component() {
|
|
13
|
-
* const state = createState({ count: 0 });
|
|
14
|
-
* const helpers = { increment: () => state.count++ };
|
|
15
|
-
* const view = createView(state, helpers);
|
|
16
|
-
* const { count, increment } = view; // Don't do this!
|
|
17
|
-
* return () => <button onClick={increment}>{count}</button>; // Won't update!
|
|
18
|
-
* }
|
|
19
|
-
*
|
|
20
|
-
* // ✅ Good - access properties directly in render
|
|
21
|
-
* function Component() {
|
|
22
|
-
* const state = createState({ count: 0 });
|
|
23
|
-
* const helpers = { increment: () => state.count++ };
|
|
24
|
-
* const view = createView(state, helpers);
|
|
25
|
-
* return () => <button onClick={view.increment}>{view.count}</button>; // Reactive!
|
|
26
|
-
* }
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* // Merge multiple reactive objects
|
|
30
|
-
* const state = createState({ count: 0 });
|
|
31
|
-
* const user = createState({ name: "Alice" });
|
|
32
|
-
* const view = createView(state, user);
|
|
33
|
-
* // view has both count and name properties, maintaining reactivity
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* // Later arguments override earlier ones
|
|
37
|
-
* const a = { x: 1, y: 2 };
|
|
38
|
-
* const b = { y: 3, z: 4 };
|
|
39
|
-
* const view = createView(a, b);
|
|
40
|
-
* // view.x === 1, view.y === 3, view.z === 4
|
|
41
|
-
*
|
|
42
|
-
* @param args - Objects to merge (reactive or plain objects)
|
|
43
|
-
* @returns A view object with getters for all properties, maintaining reactivity
|
|
44
|
-
*/
|
|
45
2
|
export function createView(...args) {
|
|
46
3
|
const result = {};
|
|
47
4
|
const seen = new Set();
|
|
48
5
|
let notifyInspectorRef = {};
|
|
49
6
|
for (let i = args.length - 1; i >= 0; i--) {
|
|
50
7
|
const src = args[i];
|
|
8
|
+
if (!src)
|
|
9
|
+
continue;
|
|
51
10
|
if (INSPECTOR_ENABLED && src[INSPECT_MARKER]) {
|
|
52
11
|
src[INSPECT_MARKER] = notifyInspectorRef;
|
|
53
12
|
}
|
|
54
|
-
//
|
|
13
|
+
// Mimic Object.assign: only enumerable own property keys
|
|
55
14
|
for (const key of Reflect.ownKeys(src)) {
|
|
56
15
|
if (seen.has(key))
|
|
57
16
|
continue;
|
|
58
17
|
const desc = Object.getOwnPropertyDescriptor(src, key);
|
|
59
18
|
if (!desc || !desc.enumerable)
|
|
60
19
|
continue;
|
|
61
|
-
// Capture the current source for this key (last write wins).
|
|
62
20
|
Object.defineProperty(result, key, {
|
|
63
21
|
enumerable: true,
|
|
64
22
|
configurable: true,
|
|
@@ -67,6 +25,7 @@ export function createView(...args) {
|
|
|
67
25
|
if (!INSPECTOR_ENABLED || !notifyInspectorRef.current) {
|
|
68
26
|
return value;
|
|
69
27
|
}
|
|
28
|
+
// Propagate inspector marker into nested observables
|
|
70
29
|
if (value?.[INSPECT_MARKER]) {
|
|
71
30
|
value[INSPECT_MARKER] = {
|
|
72
31
|
current: {
|
|
@@ -76,6 +35,7 @@ export function createView(...args) {
|
|
|
76
35
|
};
|
|
77
36
|
}
|
|
78
37
|
else if (typeof value === "function") {
|
|
38
|
+
// Wrap actions to notify inspector
|
|
79
39
|
return (...params) => {
|
|
80
40
|
notifyInspectorRef.current.notify({
|
|
81
41
|
type: "action",
|
|
@@ -100,6 +60,7 @@ export function createView(...args) {
|
|
|
100
60
|
},
|
|
101
61
|
set: (value) => {
|
|
102
62
|
Object.defineProperty(notifyInspectorRef, "current", {
|
|
63
|
+
configurable: true,
|
|
103
64
|
get() {
|
|
104
65
|
return value.current;
|
|
105
66
|
},
|
|
@@ -107,5 +68,6 @@ export function createView(...args) {
|
|
|
107
68
|
},
|
|
108
69
|
});
|
|
109
70
|
}
|
|
71
|
+
// The overload signatures expose a precise type; this is the shared impl.
|
|
110
72
|
return result;
|
|
111
73
|
}
|