@zeix/cause-effect 0.12.2 → 0.12.3
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 +44 -2
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/index.ts +1 -1
- package/lib/scheduler.d.ts +6 -7
- package/lib/scheduler.ts +25 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cause & Effect
|
|
2
2
|
|
|
3
|
-
Version 0.12.
|
|
3
|
+
Version 0.12.3
|
|
4
4
|
|
|
5
5
|
**Cause & Effect** is a lightweight, reactive state management library for JavaScript applications. It uses the concept of signals to create a predictable and efficient data flow in your app.
|
|
6
6
|
|
|
@@ -54,7 +54,7 @@ npm install @zeix/cause-effect
|
|
|
54
54
|
bun add @zeix/cause-effect
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
##
|
|
57
|
+
## Usage
|
|
58
58
|
|
|
59
59
|
### Single State Signal
|
|
60
60
|
|
|
@@ -214,3 +214,45 @@ This example showcases several powerful features of Cause & Effect:
|
|
|
214
214
|
5. **Flexibility and Integration**: Seamlessly integrates with DOM manipulation and event listeners, fitting into any JavaScript application or framework.
|
|
215
215
|
|
|
216
216
|
These principles enable developers to create complex, reactive applications with clear data flow, efficient updates, and robust error handling, while promoting code reuse and modularity.
|
|
217
|
+
|
|
218
|
+
### Effects and DOM Updates
|
|
219
|
+
|
|
220
|
+
The `enqueue()` function allows you to schedule DOM updates to be executed on the next animation frame. This function returns a `Promise`, which makes it easy to detect when updates are applied or if they fail.
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
import { enqueue } from '@zeix/cause-effect'
|
|
224
|
+
|
|
225
|
+
// Schedule a DOM update
|
|
226
|
+
enqueue(() => {
|
|
227
|
+
document.getElementById('myElement').textContent = 'Updated content'
|
|
228
|
+
})
|
|
229
|
+
.then(() => console.log('Update applied successfully'))
|
|
230
|
+
.catch(error => console.error('Update failed:', error))
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
You can also use the deduplication feature to ensure that only the latest update for a specific element and operation is applied:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
import { enqueue } from '@zeix/cause-effect'
|
|
237
|
+
|
|
238
|
+
// Define a signal and update it in an event handler
|
|
239
|
+
const name = state('')
|
|
240
|
+
document.querySelector('input[name="name"]').addEventListener('input', e => {
|
|
241
|
+
state.set(e.target.value) // Triggers an update on every keystroke
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// Define an effect to react to signal changes
|
|
245
|
+
effect(text => {
|
|
246
|
+
const nameSpan = document.querySelector('.greeting .name')
|
|
247
|
+
enqueue(() => {
|
|
248
|
+
nameSpan.textContent = text
|
|
249
|
+
return text
|
|
250
|
+
}, [nameSpan, 'setName']) // For deduplication
|
|
251
|
+
.then(result => console.log(`Name was updated to ${result}`))
|
|
252
|
+
.catch(error => console.error('Failed to update name:', error))
|
|
253
|
+
}, name)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
In this example, as the user types in the input field only 'Jane' will be applied to the DOM. 'J', 'Ja', 'Jan' were superseded by more recent updates and deduplicated (if typing was fast enough).
|
|
257
|
+
|
|
258
|
+
When multiple `enqueue` calls are made with the same deduplication key before the next animation frame, only the last call will be executed. Previous calls are superseded and their Promises will not be resolved or rejected. This "last-write-wins" behavior ensures that only the most recent update is applied, which is typically desirable for UI updates and state changes.
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var V=(x)=>typeof x==="function";var
|
|
1
|
+
var V=(x)=>typeof x==="function";var q=(x,B)=>Object.prototype.toString.call(x)===`[object ${B}]`,m=(x)=>(B)=>B instanceof x,j=m(Error),g=m(Promise),y=(x)=>j(x)?x:new Error(String(x));var F,R=new Set,T=0,w=new Map,N,p=()=>{N=void 0;let x=Array.from(w.values());w.clear();for(let B of x)B()},o=()=>{if(N)cancelAnimationFrame(N);N=requestAnimationFrame(p)};queueMicrotask(p);var D=(x)=>{if(F&&!x.includes(F))x.push(F)},C=(x)=>{for(let B of x)T?R.add(B):B()},S=()=>{while(R.size){let x=Array.from(R);R.clear();for(let B of x)B()}},v=(x)=>{T++;try{x()}finally{S(),T--}},O=(x,B)=>{let z=F;F=B;try{x()}finally{F=z}},n=(x,B)=>new Promise((z,$)=>{let L=()=>{try{z(x())}catch(W){$(W)}};if(B)w.set(B,L);o()});function Y(x,...B){let z=!1,$=()=>O(()=>{if(z)throw new Error("Circular dependency in effect detected");z=!0;let L=_(B,x);if(j(L))console.error("Unhandled error in effect:",L);z=!1},$);$()}var b="Computed",i=(x,B)=>{if(!B)return!1;return x.name===B.name&&x.message===B.message},I=(x,...B)=>{let z=[],$=K,L,W=!0,H=!1,Z=!1,J=(G)=>{if(!Object.is(G,$))$=G,W=!1,L=void 0,H=!1},Q=()=>{H=K===$,$=K,L=void 0},X=(G)=>{let P=y(G);H=i(P,L),$=K,L=P},A=()=>{if(W=!0,!H)C(z)},d=()=>O(()=>{if(Z)throw new Error("Circular dependency in computed detected");H=!0,Z=!0;let G=_(B,x);if(g(G))Q(),G.then((P)=>{J(P),C(z)}).catch(X);else if(G==null||K===G)Q();else if(j(G))X(G);else J(G);Z=!1},A),M={[Symbol.toStringTag]:b,get:()=>{if(D(z),S(),W)d();if(L)throw L;return $},map:(G)=>I(G,M),match:(G)=>{return Y(G,M),M}};return M},U=(x)=>q(x,b);var h="State",k=(x)=>{let B=[],z=x,$={[Symbol.toStringTag]:h,get:()=>{return D(B),z},set:(L)=>{if(Object.is(z,L))return;if(z=L,C(B),K===z)B.length=0},update:(L)=>{$.set(L(z))},map:(L)=>I(L,$),match:(L)=>{return Y(L,$),$}};return $},E=(x)=>q(x,h);var K=Symbol(),c=(x)=>V(x)&&!x.length||typeof x==="object"&&x!==null&&("ok"in x)&&V(x.ok),f=(x)=>E(x)||U(x),t=(x)=>f(x)?x:c(x)?I(x):k(x),_=(x,B)=>{let{ok:z,nil:$,err:L}=V(B)?{ok:B}:B,W=[],H=[],Z=!1;for(let Q=0;Q<x.length;Q++){let X=x[Q];try{let A=f(X)?X.get():V(X)?X():X;if(A===K)Z=!0;W[Q]=A}catch(A){H.push(y(A))}}let J=void 0;try{if(Z&&$)J=$();else if(H.length)J=L?L(...H):H[0];else if(!Z)J=z(...W)}catch(Q){if(J=y(Q),L)J=L(J)}return J};export{O as watch,t as toSignal,k as state,E as isState,f as isSignal,U as isComputed,n as enqueue,Y as effect,I as computed,v as batch,K as UNSET};
|
package/index.ts
CHANGED
package/lib/scheduler.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type EnqueueDedupe = [Element, string];
|
|
2
2
|
export type Watcher = () => void;
|
|
3
|
-
export type Updater = <T>() => T;
|
|
3
|
+
export type Updater = <T>() => T | boolean | void;
|
|
4
4
|
/**
|
|
5
5
|
* Add active watcher to the array of watchers
|
|
6
6
|
*
|
|
@@ -27,14 +27,13 @@ export declare const batch: (fn: () => void) => void;
|
|
|
27
27
|
* Run a function in a reactive context
|
|
28
28
|
*
|
|
29
29
|
* @param {() => void} run - function to run the computation or effect
|
|
30
|
-
* @param {Watcher} mark - function to be called when the state changes
|
|
30
|
+
* @param {Watcher} mark - function to be called when the state changes or undefined for temporary unwatching while inserting auto-hydrating DOM nodes that might read signals (e.g., web components)
|
|
31
31
|
*/
|
|
32
|
-
export declare const watch: (run: () => void, mark
|
|
32
|
+
export declare const watch: (run: () => void, mark?: Watcher) => void;
|
|
33
33
|
/**
|
|
34
34
|
* Enqueue a function to be executed on the next animation frame
|
|
35
35
|
*
|
|
36
|
-
* @param
|
|
37
|
-
* @param dedupe
|
|
38
|
-
* @returns
|
|
36
|
+
* @param {Updater} fn - function to be executed on the next animation frame; can return updated value <T>, success <boolean> or void
|
|
37
|
+
* @param {EnqueueDedupe} dedupe - [element, operation] pair for deduplication
|
|
39
38
|
*/
|
|
40
|
-
export declare const enqueue: <T>(
|
|
39
|
+
export declare const enqueue: <T>(fn: Updater, dedupe?: EnqueueDedupe) => Promise<boolean | void | T>;
|
package/lib/scheduler.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
export type EnqueueDedupe = [Element, string]
|
|
4
4
|
|
|
5
5
|
export type Watcher = () => void
|
|
6
|
-
export type Updater = <T>() => T
|
|
6
|
+
export type Updater = <T>() => T | boolean | void
|
|
7
7
|
|
|
8
8
|
/* === Internal === */
|
|
9
9
|
|
|
@@ -15,16 +15,15 @@ const pending = new Set<Watcher>()
|
|
|
15
15
|
let batchDepth = 0
|
|
16
16
|
|
|
17
17
|
// Map of DOM elements to update functions
|
|
18
|
-
const updateMap = new Map<
|
|
18
|
+
const updateMap = new Map<EnqueueDedupe, () => void>()
|
|
19
19
|
let requestId: number | undefined
|
|
20
20
|
|
|
21
21
|
const updateDOM = () => {
|
|
22
22
|
requestId = undefined
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
elementMap.clear()
|
|
23
|
+
const updates = Array.from(updateMap.values())
|
|
24
|
+
updateMap.clear()
|
|
25
|
+
for (const fn of updates) {
|
|
26
|
+
fn()
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
29
|
|
|
@@ -81,47 +80,49 @@ export const flush = () => {
|
|
|
81
80
|
*/
|
|
82
81
|
export const batch = (fn: () => void) => {
|
|
83
82
|
batchDepth++
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
try {
|
|
84
|
+
fn()
|
|
85
|
+
} finally {
|
|
86
|
+
flush()
|
|
87
|
+
batchDepth--
|
|
88
|
+
}
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
/**
|
|
90
92
|
* Run a function in a reactive context
|
|
91
93
|
*
|
|
92
94
|
* @param {() => void} run - function to run the computation or effect
|
|
93
|
-
* @param {Watcher} mark - function to be called when the state changes
|
|
95
|
+
* @param {Watcher} mark - function to be called when the state changes or undefined for temporary unwatching while inserting auto-hydrating DOM nodes that might read signals (e.g., web components)
|
|
94
96
|
*/
|
|
95
|
-
export const watch = (run: () => void, mark
|
|
97
|
+
export const watch = (run: () => void, mark?: Watcher): void => {
|
|
96
98
|
const prev = active
|
|
97
99
|
active = mark
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
try {
|
|
101
|
+
run()
|
|
102
|
+
} finally {
|
|
103
|
+
active = prev
|
|
104
|
+
}
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
/**
|
|
103
108
|
* Enqueue a function to be executed on the next animation frame
|
|
104
109
|
*
|
|
105
|
-
* @param
|
|
106
|
-
* @param dedupe
|
|
107
|
-
* @returns
|
|
110
|
+
* @param {Updater} fn - function to be executed on the next animation frame; can return updated value <T>, success <boolean> or void
|
|
111
|
+
* @param {EnqueueDedupe} dedupe - [element, operation] pair for deduplication
|
|
108
112
|
*/
|
|
109
113
|
export const enqueue = <T>(
|
|
110
|
-
|
|
114
|
+
fn: Updater,
|
|
111
115
|
dedupe?: EnqueueDedupe
|
|
112
|
-
) => new Promise<T>((resolve, reject) => {
|
|
116
|
+
) => new Promise<T | boolean | void>((resolve, reject) => {
|
|
113
117
|
const wrappedCallback = () => {
|
|
114
118
|
try {
|
|
115
|
-
resolve(
|
|
119
|
+
resolve(fn())
|
|
116
120
|
} catch (error) {
|
|
117
121
|
reject(error)
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
if (dedupe) {
|
|
121
|
-
|
|
122
|
-
if (!updateMap.has(el)) updateMap.set(el, new Map())
|
|
123
|
-
const elementMap = updateMap.get(el)!
|
|
124
|
-
elementMap.set(op, wrappedCallback)
|
|
125
|
+
updateMap.set(dedupe, wrappedCallback)
|
|
125
126
|
}
|
|
126
127
|
requestTick()
|
|
127
128
|
})
|