@praxisjs/core 1.5.0 → 1.6.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/CHANGELOG.md +16 -11
- package/dist/signal/batch.d.ts.map +1 -1
- package/dist/signal/batch.js +11 -13
- package/dist/signal/batch.js.map +1 -1
- package/dist/signal/computed.d.ts +1 -1
- package/dist/signal/computed.d.ts.map +1 -1
- package/dist/signal/computed.js +46 -36
- package/dist/signal/computed.js.map +1 -1
- package/dist/signal/effect.d.ts.map +1 -1
- package/dist/signal/effect.js +11 -12
- package/dist/signal/effect.js.map +1 -1
- package/dist/signal/signal.d.ts +9 -0
- package/dist/signal/signal.d.ts.map +1 -1
- package/dist/signal/signal.js +71 -37
- package/dist/signal/signal.js.map +1 -1
- package/package.json +1 -1
- package/src/signal/batch.ts +9 -13
- package/src/signal/computed.ts +42 -39
- package/src/signal/effect.ts +11 -12
- package/src/signal/signal.ts +62 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @praxisjs/core
|
|
2
2
|
|
|
3
|
+
## 1.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 74d414a: Rewrite the reactivity engine for clarity and performance.
|
|
8
|
+
|
|
9
|
+
**signal()** — subscribers are now stored in a compact `SubList` representation (`null | Effect | Effect[]`) that avoids `Set` allocation for the common zero- and single-subscriber cases. `set`, `update` and `subscribe` are defined as regular closures, making the signal factory straightforward to read and extend.
|
|
10
|
+
|
|
11
|
+
**computed()** — the factory now uses a plain closure instead of the previous property-on-function pattern. Downstream computeds (chain propagation) and leaf effects from `.subscribe()` are tracked in separate holders so dirty notification never needs to inspect subscriber types. The `recompute` callback is created lazily — only when the computed is first read inside a reactive context.
|
|
12
|
+
|
|
13
|
+
**effect()** — simplified to a standard closure with named `run` and `stop` functions. The `stopped` and `cleanup` state is held naturally in the closure scope.
|
|
14
|
+
|
|
15
|
+
**batch()** — uses a pre-allocated, module-level effects array that is reused across batch calls, eliminating one `Set` allocation per `batch()` invocation.
|
|
16
|
+
|
|
17
|
+
No breaking changes to the public API (`signal`, `computed`, `effect`, `batch`, `untrack`, `peek`).
|
|
18
|
+
|
|
3
19
|
## 1.5.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
@@ -62,7 +78,6 @@
|
|
|
62
78
|
- 6c353ba: Add `untrack` utility and isolate component mounting from outer reactive contexts
|
|
63
79
|
|
|
64
80
|
**`@praxisjs/core`** exports two new functions from the public API:
|
|
65
|
-
|
|
66
81
|
- `peek(signal)` — reads a signal once without subscribing to it (was already in `/internal`, now public)
|
|
67
82
|
- `untrack(fn)` — runs a function with no active effect, suppressing all signal tracking inside it
|
|
68
83
|
|
|
@@ -79,7 +94,6 @@
|
|
|
79
94
|
```
|
|
80
95
|
|
|
81
96
|
**`@praxisjs/runtime`** — `mountComponent` now runs entirely inside `untrack`. This fixes a bug where components mounted inside a reactive context (e.g. the router) would accidentally subscribe the outer effect to any signal read during construction or render. The symptoms were:
|
|
82
|
-
|
|
83
97
|
- Eager reads like `description={this.count}` in JSX causing the router to re-mount the component on every state change, resetting state to its initial value
|
|
84
98
|
- `@Debug()` (and any decorator that reads a signal in its `addInitializer`) triggering the same re-mount loop
|
|
85
99
|
|
|
@@ -103,39 +117,31 @@
|
|
|
103
117
|
- 3372878: Migrate all packages from functional APIs to a decorator-first design.
|
|
104
118
|
|
|
105
119
|
**`@praxisjs/core`**
|
|
106
|
-
|
|
107
120
|
- Added `Composable` abstract base class for building class-based composables
|
|
108
121
|
- Removed `resource`, `createResource`, `Resource`, `ResourceStatus`, `ResourceOptions` from public exports — use `@Resource` from `@praxisjs/decorators` instead
|
|
109
122
|
|
|
110
123
|
**`@praxisjs/motion`**
|
|
111
|
-
|
|
112
124
|
- Replaced `useMotion`, `tween`, `spring`, `createTransition`, `Animate`, `easings`, `resolveEasing` with `@Tween` and `@Spring` decorators
|
|
113
125
|
|
|
114
126
|
**`@praxisjs/di`**
|
|
115
|
-
|
|
116
127
|
- Replaced `useService` and `createScope` with a `@Scope` decorator
|
|
117
128
|
- Renamed exported type `Scope` to `ScopeType` to free the name for the new decorator
|
|
118
129
|
|
|
119
130
|
**`@praxisjs/fsm`**
|
|
120
|
-
|
|
121
131
|
- Removed `createMachine` — use the `@StateMachine` and `@Transition` decorators directly
|
|
122
132
|
|
|
123
133
|
**`@praxisjs/router`**
|
|
124
|
-
|
|
125
134
|
- Removed `createRouter`, `lazy`, `useRouter`, `useParams`, `useQuery`, `useLocation`
|
|
126
135
|
- Added `@RouterConfig`, `@Lazy`, `@InjectRouter`, `@Params`, `@Query`, `@Location` decorators
|
|
127
136
|
|
|
128
137
|
**`@praxisjs/store`**
|
|
129
|
-
|
|
130
138
|
- Removed `createStore` — use the `@Store` and `@UseStore` decorators directly
|
|
131
139
|
|
|
132
140
|
**`@praxisjs/composables`**
|
|
133
|
-
|
|
134
141
|
- Replaced all `use*` composable functions with class-based composables extending `Composable`:
|
|
135
142
|
`WindowSize`, `ScrollPosition`, `ElementSize`, `Intersection`, `Focus`, `MediaQuery`, `ColorScheme`, `Mouse`, `KeyCombo`, `Idle`, `Clipboard`, `Geolocation`, `TimeAgo`, `Pagination`
|
|
136
143
|
|
|
137
144
|
**`@praxisjs/concurrent`**
|
|
138
|
-
|
|
139
145
|
- Removed `task`, `queue`, `pool` and their instance types — use `@Task`, `@Queue`, `@Pool` decorators instead
|
|
140
146
|
|
|
141
147
|
## 0.4.2
|
|
@@ -173,7 +179,6 @@
|
|
|
173
179
|
### Minor Changes
|
|
174
180
|
|
|
175
181
|
- bb0d4f8: **Refactor decorator system and component architecture across PraxisJS packages**
|
|
176
|
-
|
|
177
182
|
- Replaced legacy decorator signatures (`constructor`, `target`, `propertyKey`, method descriptor) with the standard TC39 decorator context API (`ClassDecoratorContext`, `ClassFieldDecoratorContext`, `ClassMethodDecoratorContext`) across `@praxisjs/decorators`, `@praxisjs/store`, `@praxisjs/concurrent`, `@praxisjs/router`, `@praxisjs/motion`, `@praxisjs/di`, and `@praxisjs/fsm`.
|
|
178
183
|
- Introduced `StatefulComponent` and `StatelessComponent` as the new base classes, replacing the deprecated `BaseComponent`/`Function Component` pattern, across `@praxisjs/core`, `@praxisjs/runtime`, `@praxisjs/devtools`, and templates.
|
|
179
184
|
- Implemented core rendering functionality in `@praxisjs/runtime` (`mountChildren`, `mountComponent`, reactive scope management) and removed the deprecated `renderer.ts`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../src/signal/batch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../src/signal/batch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAKvC,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,IAAI,QAWnC"}
|
package/dist/signal/batch.js
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
const batchEffects = [];
|
|
2
|
+
let batchDepth = 0;
|
|
2
3
|
export function isBatching() {
|
|
3
|
-
return
|
|
4
|
+
return batchDepth > 0;
|
|
4
5
|
}
|
|
5
6
|
export function enqueueEffect(effect) {
|
|
6
|
-
|
|
7
|
+
if (!batchEffects.includes(effect))
|
|
8
|
+
batchEffects.push(effect);
|
|
7
9
|
}
|
|
8
10
|
export function batch(fn) {
|
|
9
|
-
|
|
10
|
-
if (isOuter) {
|
|
11
|
-
batchQueue = new Set();
|
|
12
|
-
}
|
|
11
|
+
batchDepth++;
|
|
13
12
|
try {
|
|
14
13
|
fn();
|
|
15
14
|
}
|
|
16
15
|
finally {
|
|
17
|
-
if (
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
16
|
+
if (--batchDepth === 0) {
|
|
17
|
+
const n = batchEffects.length;
|
|
18
|
+
for (let i = 0; i < n; i++)
|
|
19
|
+
batchEffects[i]();
|
|
20
|
+
batchEffects.length = 0;
|
|
23
21
|
}
|
|
24
22
|
}
|
|
25
23
|
}
|
package/dist/signal/batch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/signal/batch.ts"],"names":[],"mappings":"AAEA,IAAI,UAAU,
|
|
1
|
+
{"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/signal/batch.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAAa,EAAE,CAAC;AAClC,IAAI,UAAU,GAAG,CAAC,CAAC;AAEnB,MAAM,UAAU,UAAU;IACxB,OAAO,UAAU,GAAG,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,EAAc;IAClC,UAAU,EAAE,CAAC;IACb,IAAI,CAAC;QACH,EAAE,EAAE,CAAC;IACP,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,UAAU,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC;YAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;gBAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../../src/signal/computed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../../src/signal/computed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAWjD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CA4DxD"}
|
package/dist/signal/computed.js
CHANGED
|
@@ -1,37 +1,50 @@
|
|
|
1
1
|
import { activeEffect, runEffect } from "./effect";
|
|
2
|
-
|
|
2
|
+
import { addSub, removeSub, notifySubs } from "./signal";
|
|
3
|
+
function isComputedRecompute(eff) {
|
|
4
|
+
return eff.__isComputedRecompute === true;
|
|
5
|
+
}
|
|
6
|
+
export function computed(getter) {
|
|
3
7
|
let cachedValue;
|
|
4
8
|
let dirty = true;
|
|
5
|
-
const
|
|
9
|
+
const computedHolder = { subs: null };
|
|
10
|
+
const leafHolder = { subs: null };
|
|
6
11
|
let scheduled = false;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
let recompute = null;
|
|
13
|
+
function getRecompute() {
|
|
14
|
+
if (recompute !== null)
|
|
15
|
+
return recompute;
|
|
16
|
+
recompute = function markDirty() {
|
|
17
|
+
if (dirty)
|
|
18
|
+
return;
|
|
19
|
+
dirty = true;
|
|
20
|
+
if (computedHolder.subs !== null)
|
|
21
|
+
notifySubs(computedHolder.subs, false);
|
|
22
|
+
if (leafHolder.subs !== null && !scheduled) {
|
|
23
|
+
scheduled = true;
|
|
24
|
+
queueMicrotask(() => {
|
|
25
|
+
scheduled = false;
|
|
26
|
+
if (leafHolder.subs !== null)
|
|
27
|
+
notifySubs(leafHolder.subs, false);
|
|
28
|
+
});
|
|
12
29
|
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
scheduled = false;
|
|
18
|
-
for (const sub of [...subscribers]) {
|
|
19
|
-
if (!sub.__isComputedRecompute) {
|
|
20
|
-
sub(); // notify leaf effects (DOM, @Watch, subscribe)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}, { __isComputedRecompute: true });
|
|
30
|
+
};
|
|
31
|
+
recompute.__isComputedRecompute = true;
|
|
32
|
+
return recompute;
|
|
33
|
+
}
|
|
26
34
|
function read() {
|
|
27
|
-
if (activeEffect) {
|
|
28
|
-
|
|
35
|
+
if (activeEffect !== null) {
|
|
36
|
+
if (isComputedRecompute(activeEffect)) {
|
|
37
|
+
addSub(computedHolder, activeEffect);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
addSub(leafHolder, activeEffect);
|
|
41
|
+
}
|
|
29
42
|
}
|
|
30
43
|
if (dirty) {
|
|
31
44
|
const prevEffect = activeEffect;
|
|
32
|
-
runEffect(
|
|
45
|
+
runEffect(getRecompute());
|
|
33
46
|
try {
|
|
34
|
-
cachedValue =
|
|
47
|
+
cachedValue = getter();
|
|
35
48
|
dirty = false;
|
|
36
49
|
}
|
|
37
50
|
finally {
|
|
@@ -40,17 +53,14 @@ export function computed(computeFn) {
|
|
|
40
53
|
}
|
|
41
54
|
return cachedValue;
|
|
42
55
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
computedSignal.subscribe = subscribe;
|
|
53
|
-
computedSignal.__isComputed = true;
|
|
54
|
-
return computedSignal;
|
|
56
|
+
const c = read;
|
|
57
|
+
c.subscribe = (fn) => {
|
|
58
|
+
const wrapped = () => { fn(read()); };
|
|
59
|
+
addSub(leafHolder, wrapped);
|
|
60
|
+
wrapped();
|
|
61
|
+
return () => { removeSub(leafHolder, wrapped); };
|
|
62
|
+
};
|
|
63
|
+
c.__isComputed = true;
|
|
64
|
+
return c;
|
|
55
65
|
}
|
|
56
66
|
//# sourceMappingURL=computed.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computed.js","sourceRoot":"","sources":["../../src/signal/computed.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAe,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"computed.js","sourceRoot":"","sources":["../../src/signal/computed.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAe,MAAM,UAAU,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAgB,MAAM,UAAU,CAAC;AAIvE,SAAS,mBAAmB,CAAC,GAAW;IACtC,OAAQ,GAAkC,CAAC,qBAAqB,KAAK,IAAI,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,QAAQ,CAAI,MAAe;IACzC,IAAI,WAA0B,CAAC;IAC/B,IAAI,KAAK,GAAG,IAAI,CAAC;IAEjB,MAAM,cAAc,GAAG,EAAE,IAAI,EAAE,IAAe,EAAE,CAAC;IACjD,MAAM,UAAU,GAAG,EAAE,IAAI,EAAE,IAAe,EAAE,CAAC;IAC7C,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,IAAI,SAAS,GAA6B,IAAI,CAAC;IAE/C,SAAS,YAAY;QACnB,IAAI,SAAS,KAAK,IAAI;YAAE,OAAO,SAAS,CAAC;QACzC,SAAS,GAAG,SAAS,SAAS;YAC5B,IAAI,KAAK;gBAAE,OAAO;YAClB,KAAK,GAAG,IAAI,CAAC;YACb,IAAI,cAAc,CAAC,IAAI,KAAK,IAAI;gBAAE,UAAU,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACzE,IAAI,UAAU,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBAC3C,SAAS,GAAG,IAAI,CAAC;gBACjB,cAAc,CAAC,GAAG,EAAE;oBAClB,SAAS,GAAG,KAAK,CAAC;oBAClB,IAAI,UAAU,CAAC,IAAI,KAAK,IAAI;wBAAE,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBACnE,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAsB,CAAC;QACvB,SAAS,CAAC,qBAAqB,GAAG,IAAI,CAAC;QACvC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,SAAS,IAAI;QACX,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;YAC1B,IAAI,mBAAmB,CAAC,YAAY,CAAC,EAAE,CAAC;gBACtC,MAAM,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;YACvC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QACD,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,UAAU,GAAG,YAAY,CAAC;YAChC,SAAS,CAAC,YAAY,EAAE,CAAC,CAAC;YAC1B,IAAI,CAAC;gBACH,WAAW,GAAG,MAAM,EAAE,CAAC;gBACvB,KAAK,GAAG,KAAK,CAAC;YAChB,CAAC;oBAAS,CAAC;gBACT,SAAS,CAAC,UAAU,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;QACD,OAAO,WAAgB,CAAC;IAC1B,CAAC;IAED,MAAM,CAAC,GAAG,IAAmB,CAAC;IAE9B,CAAC,CAAC,SAAS,GAAG,CAAC,EAAsB,EAAE,EAAE;QACvC,MAAM,OAAO,GAAW,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC5B,OAAO,EAAE,CAAC;QACV,OAAO,GAAG,EAAE,GAAG,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC;IAEF,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../../src/signal/effect.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC;AAEhC,eAAO,IAAI,YAAY,EAAE,MAAM,GAAG,IAAW,CAAC;AAG9C,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../../src/signal/effect.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC;AAEhC,eAAO,IAAI,YAAY,EAAE,MAAM,GAAG,IAAW,CAAC;AAG9C,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,QASnC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,QAE9C;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAQzC;AAGD,KAAK,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;AAEnC,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,OAAO,SAkBd,IAAI,CAK7B"}
|
package/dist/signal/effect.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
export let activeEffect = null;
|
|
2
2
|
const effectStack = [];
|
|
3
3
|
export function track(effect) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
activeEffect = subscriber;
|
|
4
|
+
effectStack.push(effect);
|
|
5
|
+
activeEffect = effect;
|
|
7
6
|
try {
|
|
8
|
-
|
|
7
|
+
effect();
|
|
9
8
|
}
|
|
10
9
|
finally {
|
|
11
10
|
effectStack.pop();
|
|
12
|
-
activeEffect = effectStack[effectStack.length - 1]
|
|
11
|
+
activeEffect = effectStack[effectStack.length - 1] ?? null;
|
|
13
12
|
}
|
|
14
13
|
}
|
|
15
14
|
export function runEffect(effect) {
|
|
@@ -28,21 +27,21 @@ export function untrack(fn) {
|
|
|
28
27
|
export function effect(fn) {
|
|
29
28
|
let cleanup;
|
|
30
29
|
let stopped = false;
|
|
31
|
-
|
|
30
|
+
function run() {
|
|
32
31
|
if (stopped)
|
|
33
32
|
return;
|
|
34
33
|
cleanup?.();
|
|
35
|
-
const
|
|
36
|
-
activeEffect =
|
|
34
|
+
const prev = activeEffect;
|
|
35
|
+
activeEffect = run;
|
|
37
36
|
try {
|
|
38
37
|
cleanup = fn();
|
|
39
38
|
}
|
|
40
39
|
finally {
|
|
41
|
-
activeEffect =
|
|
40
|
+
activeEffect = prev;
|
|
42
41
|
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return ()
|
|
42
|
+
}
|
|
43
|
+
run();
|
|
44
|
+
return function stop() {
|
|
46
45
|
stopped = true;
|
|
47
46
|
cleanup?.();
|
|
48
47
|
cleanup = undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../src/signal/effect.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,IAAI,YAAY,GAAkB,IAAI,CAAC;AAC9C,MAAM,WAAW,GAAa,EAAE,CAAC;AAEjC,MAAM,UAAU,KAAK,CAAC,MAAc;IAClC,
|
|
1
|
+
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../src/signal/effect.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,IAAI,YAAY,GAAkB,IAAI,CAAC;AAC9C,MAAM,WAAW,GAAa,EAAE,CAAC;AAEjC,MAAM,UAAU,KAAK,CAAC,MAAc;IAClC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzB,YAAY,GAAG,MAAM,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,EAAE,CAAC;IACX,CAAC;YAAS,CAAC;QACT,WAAW,CAAC,GAAG,EAAE,CAAC;QAClB,YAAY,GAAG,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;IAC7D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAqB;IAC7C,YAAY,GAAG,MAAM,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,OAAO,CAAI,EAAW;IACpC,MAAM,IAAI,GAAG,YAAY,CAAC;IAC1B,YAAY,GAAG,IAAI,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;AACH,CAAC;AAKD,MAAM,UAAU,MAAM,CAAC,EAAiB;IACtC,IAAI,OAAgB,CAAC;IACrB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,SAAS,GAAG;QACV,IAAI,OAAO;YAAE,OAAO;QACpB,OAAO,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,YAAY,CAAC;QAC1B,YAAY,GAAG,GAAG,CAAC;QACnB,IAAI,CAAC;YACH,OAAO,GAAG,EAAE,EAAE,CAAC;QACjB,CAAC;gBAAS,CAAC;YACT,YAAY,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,GAAG,EAAE,CAAC;IAEN,OAAO,SAAS,IAAI;QAClB,OAAO,GAAG,IAAI,CAAC;QACf,OAAO,EAAE,EAAE,CAAC;QACZ,OAAO,GAAG,SAAS,CAAC;IACtB,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/signal/signal.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import type { Signal } from "@praxisjs/shared";
|
|
2
|
+
import { type Effect } from "./effect";
|
|
3
|
+
export type SubList = Effect | Effect[] | null;
|
|
4
|
+
export declare function addSub(holder: {
|
|
5
|
+
subs: SubList;
|
|
6
|
+
}, eff: Effect): void;
|
|
7
|
+
export declare function removeSub(holder: {
|
|
8
|
+
subs: SubList;
|
|
9
|
+
}, eff: Effect): void;
|
|
10
|
+
export declare function notifySubs(subs: Exclude<SubList, null>, batching: boolean): void;
|
|
2
11
|
export declare function signal<T>(initialValue: T): Signal<T>;
|
|
3
12
|
//# sourceMappingURL=signal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAErD,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;AAE/C,wBAAgB,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CASnE;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAStE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAoBhF;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CA4BpD"}
|
package/dist/signal/signal.js
CHANGED
|
@@ -1,50 +1,84 @@
|
|
|
1
1
|
import { isBatching, enqueueEffect } from "./batch";
|
|
2
2
|
import { activeEffect } from "./effect";
|
|
3
|
+
export function addSub(holder, eff) {
|
|
4
|
+
const subs = holder.subs;
|
|
5
|
+
if (subs === null) {
|
|
6
|
+
holder.subs = eff;
|
|
7
|
+
}
|
|
8
|
+
else if (typeof subs === "function") {
|
|
9
|
+
if (subs !== eff)
|
|
10
|
+
holder.subs = [subs, eff];
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
if (!subs.includes(eff))
|
|
14
|
+
subs.push(eff);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function removeSub(holder, eff) {
|
|
18
|
+
const subs = holder.subs;
|
|
19
|
+
if (subs === null)
|
|
20
|
+
return;
|
|
21
|
+
if (typeof subs === "function") {
|
|
22
|
+
if (subs === eff)
|
|
23
|
+
holder.subs = null;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const idx = subs.indexOf(eff);
|
|
27
|
+
if (idx >= 0)
|
|
28
|
+
subs.splice(idx, 1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function notifySubs(subs, batching) {
|
|
32
|
+
if (typeof subs === "function") {
|
|
33
|
+
if (batching)
|
|
34
|
+
enqueueEffect(subs);
|
|
35
|
+
else
|
|
36
|
+
subs();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (batching) {
|
|
40
|
+
for (const sub of subs)
|
|
41
|
+
enqueueEffect(sub);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let lastError;
|
|
45
|
+
let hasError = false;
|
|
46
|
+
for (const sub of subs) {
|
|
47
|
+
try {
|
|
48
|
+
sub();
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
lastError = e;
|
|
52
|
+
hasError = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (hasError)
|
|
56
|
+
throw lastError;
|
|
57
|
+
}
|
|
3
58
|
export function signal(initialValue) {
|
|
4
59
|
let value = initialValue;
|
|
5
|
-
const
|
|
60
|
+
const holder = { subs: null };
|
|
6
61
|
function read() {
|
|
7
|
-
if (activeEffect)
|
|
8
|
-
|
|
9
|
-
}
|
|
62
|
+
if (activeEffect !== null)
|
|
63
|
+
addSub(holder, activeEffect);
|
|
10
64
|
return value;
|
|
11
65
|
}
|
|
12
|
-
|
|
66
|
+
const s = read;
|
|
67
|
+
s.set = (newValue) => {
|
|
13
68
|
if (Object.is(value, newValue))
|
|
14
69
|
return;
|
|
15
70
|
value = newValue;
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
sub();
|
|
24
|
-
}
|
|
25
|
-
catch (e) {
|
|
26
|
-
errors.push(e);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
if (errors.length > 0)
|
|
30
|
-
throw errors[errors.length - 1];
|
|
31
|
-
}
|
|
32
|
-
function update(fn) {
|
|
33
|
-
set(fn(value));
|
|
34
|
-
}
|
|
35
|
-
function subscribe(fn) {
|
|
36
|
-
const wrapped = () => {
|
|
37
|
-
fn(value);
|
|
38
|
-
};
|
|
39
|
-
subscribers.add(wrapped);
|
|
71
|
+
if (holder.subs !== null)
|
|
72
|
+
notifySubs(holder.subs, isBatching());
|
|
73
|
+
};
|
|
74
|
+
s.update = (fn) => { s.set(fn(value)); };
|
|
75
|
+
s.subscribe = (fn) => {
|
|
76
|
+
const wrapped = () => { fn(value); };
|
|
77
|
+
addSub(holder, wrapped);
|
|
40
78
|
wrapped();
|
|
41
|
-
return () =>
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
signal.update = update;
|
|
46
|
-
signal.subscribe = subscribe;
|
|
47
|
-
signal.__isSignal = true;
|
|
48
|
-
return signal;
|
|
79
|
+
return () => { removeSub(holder, wrapped); };
|
|
80
|
+
};
|
|
81
|
+
s.__isSignal = true;
|
|
82
|
+
return s;
|
|
49
83
|
}
|
|
50
84
|
//# sourceMappingURL=signal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,YAAY,EAAe,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/signal/batch.ts
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
import type { Effect } from "./effect";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const batchEffects: Effect[] = [];
|
|
4
|
+
let batchDepth = 0;
|
|
4
5
|
|
|
5
6
|
export function isBatching(): boolean {
|
|
6
|
-
return
|
|
7
|
+
return batchDepth > 0;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function enqueueEffect(effect: Effect): void {
|
|
10
|
-
|
|
11
|
+
if (!batchEffects.includes(effect)) batchEffects.push(effect);
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function batch(fn: () => void) {
|
|
14
|
-
|
|
15
|
-
if (isOuter) {
|
|
16
|
-
batchQueue = new Set();
|
|
17
|
-
}
|
|
15
|
+
batchDepth++;
|
|
18
16
|
try {
|
|
19
17
|
fn();
|
|
20
18
|
} finally {
|
|
21
|
-
if (
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
eff();
|
|
26
|
-
});
|
|
19
|
+
if (--batchDepth === 0) {
|
|
20
|
+
const n = batchEffects.length;
|
|
21
|
+
for (let i = 0; i < n; i++) batchEffects[i]();
|
|
22
|
+
batchEffects.length = 0;
|
|
27
23
|
}
|
|
28
24
|
}
|
|
29
25
|
}
|
package/src/signal/computed.ts
CHANGED
|
@@ -1,69 +1,72 @@
|
|
|
1
1
|
import type { Computed } from "@praxisjs/shared";
|
|
2
2
|
|
|
3
3
|
import { activeEffect, runEffect, type Effect } from "./effect";
|
|
4
|
+
import { addSub, removeSub, notifySubs, type SubList } from "./signal";
|
|
4
5
|
|
|
5
6
|
type ComputedRecompute = Effect & { __isComputedRecompute: true };
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
function isComputedRecompute(eff: Effect): boolean {
|
|
9
|
+
return (eff as Partial<ComputedRecompute>).__isComputedRecompute === true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function computed<T>(getter: () => T): Computed<T> {
|
|
13
|
+
let cachedValue: T | undefined;
|
|
9
14
|
let dirty = true;
|
|
10
|
-
|
|
15
|
+
|
|
16
|
+
const computedHolder = { subs: null as SubList };
|
|
17
|
+
const leafHolder = { subs: null as SubList };
|
|
11
18
|
let scheduled = false;
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
let recompute: ComputedRecompute | null = null;
|
|
21
|
+
|
|
22
|
+
function getRecompute(): ComputedRecompute {
|
|
23
|
+
if (recompute !== null) return recompute;
|
|
24
|
+
recompute = function markDirty(): void {
|
|
25
|
+
if (dirty) return;
|
|
15
26
|
dirty = true;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
sub(); // propagate dirty synchronously through computed chain
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
if (!scheduled) {
|
|
27
|
+
if (computedHolder.subs !== null) notifySubs(computedHolder.subs, false);
|
|
28
|
+
if (leafHolder.subs !== null && !scheduled) {
|
|
22
29
|
scheduled = true;
|
|
23
30
|
queueMicrotask(() => {
|
|
24
31
|
scheduled = false;
|
|
25
|
-
|
|
26
|
-
if (!(sub as Partial<ComputedRecompute>).__isComputedRecompute) {
|
|
27
|
-
sub(); // notify leaf effects (DOM, @Watch, subscribe)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
32
|
+
if (leafHolder.subs !== null) notifySubs(leafHolder.subs, false);
|
|
30
33
|
});
|
|
31
34
|
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
} as ComputedRecompute;
|
|
36
|
+
recompute.__isComputedRecompute = true;
|
|
37
|
+
return recompute;
|
|
38
|
+
}
|
|
35
39
|
|
|
36
|
-
function read() {
|
|
37
|
-
if (activeEffect) {
|
|
38
|
-
|
|
40
|
+
function read(): T {
|
|
41
|
+
if (activeEffect !== null) {
|
|
42
|
+
if (isComputedRecompute(activeEffect)) {
|
|
43
|
+
addSub(computedHolder, activeEffect);
|
|
44
|
+
} else {
|
|
45
|
+
addSub(leafHolder, activeEffect);
|
|
46
|
+
}
|
|
39
47
|
}
|
|
40
|
-
|
|
41
48
|
if (dirty) {
|
|
42
49
|
const prevEffect = activeEffect;
|
|
43
|
-
runEffect(
|
|
50
|
+
runEffect(getRecompute());
|
|
44
51
|
try {
|
|
45
|
-
cachedValue =
|
|
52
|
+
cachedValue = getter();
|
|
46
53
|
dirty = false;
|
|
47
54
|
} finally {
|
|
48
55
|
runEffect(prevEffect);
|
|
49
56
|
}
|
|
50
57
|
}
|
|
51
|
-
|
|
52
|
-
return cachedValue;
|
|
58
|
+
return cachedValue as T;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
const wrappedEffect = () => {
|
|
57
|
-
fn(read());
|
|
58
|
-
};
|
|
59
|
-
subscribers.add(wrappedEffect);
|
|
60
|
-
wrappedEffect();
|
|
61
|
-
return () => subscribers.delete(wrappedEffect);
|
|
62
|
-
}
|
|
61
|
+
const c = read as Computed<T>;
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
c.subscribe = (fn: (value: T) => void) => {
|
|
64
|
+
const wrapped: Effect = () => { fn(read()); };
|
|
65
|
+
addSub(leafHolder, wrapped);
|
|
66
|
+
wrapped();
|
|
67
|
+
return () => { removeSub(leafHolder, wrapped); };
|
|
68
|
+
};
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
c.__isComputed = true;
|
|
71
|
+
return c;
|
|
69
72
|
}
|
package/src/signal/effect.ts
CHANGED
|
@@ -4,14 +4,13 @@ export let activeEffect: Effect | null = null;
|
|
|
4
4
|
const effectStack: Effect[] = [];
|
|
5
5
|
|
|
6
6
|
export function track(effect: Effect) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
activeEffect = subscriber;
|
|
7
|
+
effectStack.push(effect);
|
|
8
|
+
activeEffect = effect;
|
|
10
9
|
try {
|
|
11
|
-
|
|
10
|
+
effect();
|
|
12
11
|
} finally {
|
|
13
12
|
effectStack.pop();
|
|
14
|
-
activeEffect = effectStack[effectStack.length - 1]
|
|
13
|
+
activeEffect = effectStack[effectStack.length - 1] ?? null;
|
|
15
14
|
}
|
|
16
15
|
}
|
|
17
16
|
|
|
@@ -36,21 +35,21 @@ export function effect(fn: () => Cleanup) {
|
|
|
36
35
|
let cleanup: Cleanup;
|
|
37
36
|
let stopped = false;
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
function run(): void {
|
|
40
39
|
if (stopped) return;
|
|
41
40
|
cleanup?.();
|
|
42
|
-
const
|
|
43
|
-
activeEffect =
|
|
41
|
+
const prev = activeEffect;
|
|
42
|
+
activeEffect = run;
|
|
44
43
|
try {
|
|
45
44
|
cleanup = fn();
|
|
46
45
|
} finally {
|
|
47
|
-
activeEffect =
|
|
46
|
+
activeEffect = prev;
|
|
48
47
|
}
|
|
49
|
-
}
|
|
48
|
+
}
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
run();
|
|
52
51
|
|
|
53
|
-
return ()
|
|
52
|
+
return function stop(): void {
|
|
54
53
|
stopped = true;
|
|
55
54
|
cleanup?.();
|
|
56
55
|
cleanup = undefined;
|
package/src/signal/signal.ts
CHANGED
|
@@ -3,53 +3,78 @@ import type { Signal } from "@praxisjs/shared";
|
|
|
3
3
|
import { isBatching, enqueueEffect } from "./batch";
|
|
4
4
|
import { activeEffect, type Effect } from "./effect";
|
|
5
5
|
|
|
6
|
+
export type SubList = Effect | Effect[] | null;
|
|
7
|
+
|
|
8
|
+
export function addSub(holder: { subs: SubList }, eff: Effect): void {
|
|
9
|
+
const subs = holder.subs;
|
|
10
|
+
if (subs === null) {
|
|
11
|
+
holder.subs = eff;
|
|
12
|
+
} else if (typeof subs === "function") {
|
|
13
|
+
if (subs !== eff) holder.subs = [subs, eff];
|
|
14
|
+
} else {
|
|
15
|
+
if (!subs.includes(eff)) subs.push(eff);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function removeSub(holder: { subs: SubList }, eff: Effect): void {
|
|
20
|
+
const subs = holder.subs;
|
|
21
|
+
if (subs === null) return;
|
|
22
|
+
if (typeof subs === "function") {
|
|
23
|
+
if (subs === eff) holder.subs = null;
|
|
24
|
+
} else {
|
|
25
|
+
const idx = subs.indexOf(eff);
|
|
26
|
+
if (idx >= 0) subs.splice(idx, 1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function notifySubs(subs: Exclude<SubList, null>, batching: boolean): void {
|
|
31
|
+
if (typeof subs === "function") {
|
|
32
|
+
if (batching) enqueueEffect(subs); else subs();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (batching) {
|
|
36
|
+
for (const sub of subs) enqueueEffect(sub);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let lastError: unknown;
|
|
40
|
+
let hasError = false;
|
|
41
|
+
for (const sub of subs) {
|
|
42
|
+
try {
|
|
43
|
+
sub();
|
|
44
|
+
} catch (e) {
|
|
45
|
+
lastError = e;
|
|
46
|
+
hasError = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (hasError) throw lastError;
|
|
50
|
+
}
|
|
51
|
+
|
|
6
52
|
export function signal<T>(initialValue: T): Signal<T> {
|
|
7
53
|
let value = initialValue;
|
|
8
|
-
const
|
|
54
|
+
const holder = { subs: null as SubList };
|
|
9
55
|
|
|
10
|
-
function read() {
|
|
11
|
-
if (activeEffect)
|
|
12
|
-
subscribers.add(activeEffect);
|
|
13
|
-
}
|
|
56
|
+
function read(): T {
|
|
57
|
+
if (activeEffect !== null) addSub(holder, activeEffect);
|
|
14
58
|
return value;
|
|
15
59
|
}
|
|
16
60
|
|
|
17
|
-
|
|
61
|
+
const s = read as Signal<T>;
|
|
62
|
+
|
|
63
|
+
s.set = (newValue: T) => {
|
|
18
64
|
if (Object.is(value, newValue)) return;
|
|
19
65
|
value = newValue;
|
|
20
|
-
if (isBatching())
|
|
21
|
-
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
const errors: unknown[] = [];
|
|
25
|
-
[...subscribers].forEach((sub) => {
|
|
26
|
-
try {
|
|
27
|
-
sub();
|
|
28
|
-
} catch (e) {
|
|
29
|
-
errors.push(e);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
if (errors.length > 0) throw errors[errors.length - 1];
|
|
33
|
-
}
|
|
66
|
+
if (holder.subs !== null) notifySubs(holder.subs, isBatching());
|
|
67
|
+
};
|
|
34
68
|
|
|
35
|
-
|
|
36
|
-
set(fn(value));
|
|
37
|
-
}
|
|
69
|
+
s.update = (fn: (prev: T) => T) => { s.set(fn(value)); };
|
|
38
70
|
|
|
39
|
-
|
|
40
|
-
const wrapped = () => {
|
|
41
|
-
|
|
42
|
-
};
|
|
43
|
-
subscribers.add(wrapped);
|
|
71
|
+
s.subscribe = (fn: (value: T) => void) => {
|
|
72
|
+
const wrapped: Effect = () => { fn(value); };
|
|
73
|
+
addSub(holder, wrapped);
|
|
44
74
|
wrapped();
|
|
45
|
-
return () =>
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const signal = read as Signal<T>;
|
|
49
|
-
signal.set = set;
|
|
50
|
-
signal.update = update;
|
|
51
|
-
signal.subscribe = subscribe;
|
|
52
|
-
signal.__isSignal = true;
|
|
75
|
+
return () => { removeSub(holder, wrapped); };
|
|
76
|
+
};
|
|
53
77
|
|
|
54
|
-
|
|
78
|
+
s.__isSignal = true;
|
|
79
|
+
return s;
|
|
55
80
|
}
|