@fictjs/runtime 0.0.9 → 0.0.11
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/dist/index.cjs +398 -204
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.dev.js +400 -204
- package/dist/index.dev.js.map +1 -1
- package/dist/index.js +398 -204
- package/dist/index.js.map +1 -1
- package/dist/slim.cjs +398 -204
- package/dist/slim.cjs.map +1 -1
- package/dist/slim.d.cts +1 -0
- package/dist/slim.d.ts +1 -0
- package/dist/slim.js +398 -204
- package/dist/slim.js.map +1 -1
- package/package.json +1 -1
- package/src/binding.ts +41 -13
- package/src/dom.ts +16 -6
- package/src/effect.ts +18 -6
- package/src/hooks.ts +14 -1
- package/src/list-helpers.ts +125 -47
- package/src/signal.ts +102 -4
package/src/signal.ts
CHANGED
|
@@ -96,6 +96,10 @@ export interface EffectNode extends BaseNode {
|
|
|
96
96
|
deps: Link | undefined
|
|
97
97
|
/** Last dependency link */
|
|
98
98
|
depsTail: Link | undefined
|
|
99
|
+
/** Optional cleanup runner to be called before checkDirty */
|
|
100
|
+
runCleanup?: () => void
|
|
101
|
+
/** Devtools ID */
|
|
102
|
+
__id?: number | undefined
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
/**
|
|
@@ -202,6 +206,8 @@ const enqueueMicrotask =
|
|
|
202
206
|
: (fn: () => void) => {
|
|
203
207
|
Promise.resolve().then(fn)
|
|
204
208
|
}
|
|
209
|
+
// Flag to indicate cleanup is running - signal reads should return currentValue without updating
|
|
210
|
+
let inCleanup = false
|
|
205
211
|
export const ReactiveFlags = {
|
|
206
212
|
None: 0,
|
|
207
213
|
Mutable,
|
|
@@ -735,7 +741,16 @@ function updateComputed<T>(c: ComputedNode<T>): boolean {
|
|
|
735
741
|
*/
|
|
736
742
|
function runEffect(e: EffectNode): void {
|
|
737
743
|
const flags = e.flags
|
|
738
|
-
|
|
744
|
+
// Run cleanup BEFORE checkDirty so cleanup sees previous signal values
|
|
745
|
+
if (flags & Dirty) {
|
|
746
|
+
if (e.runCleanup) {
|
|
747
|
+
inCleanup = true
|
|
748
|
+
try {
|
|
749
|
+
e.runCleanup()
|
|
750
|
+
} finally {
|
|
751
|
+
inCleanup = false
|
|
752
|
+
}
|
|
753
|
+
}
|
|
739
754
|
++cycle
|
|
740
755
|
effectRunDevtools(e)
|
|
741
756
|
e.depsTail = undefined
|
|
@@ -752,6 +767,36 @@ function runEffect(e: EffectNode): void {
|
|
|
752
767
|
e.flags = Watching
|
|
753
768
|
throw err
|
|
754
769
|
}
|
|
770
|
+
} else if (flags & Pending && e.deps) {
|
|
771
|
+
// Run cleanup before checkDirty which commits signal values
|
|
772
|
+
if (e.runCleanup) {
|
|
773
|
+
inCleanup = true
|
|
774
|
+
try {
|
|
775
|
+
e.runCleanup()
|
|
776
|
+
} finally {
|
|
777
|
+
inCleanup = false
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (checkDirty(e.deps, e)) {
|
|
781
|
+
++cycle
|
|
782
|
+
effectRunDevtools(e)
|
|
783
|
+
e.depsTail = undefined
|
|
784
|
+
e.flags = WatchingRunning
|
|
785
|
+
const prevSub = activeSub
|
|
786
|
+
activeSub = e
|
|
787
|
+
try {
|
|
788
|
+
e.fn()
|
|
789
|
+
activeSub = prevSub
|
|
790
|
+
e.flags = Watching
|
|
791
|
+
purgeDeps(e)
|
|
792
|
+
} catch (err) {
|
|
793
|
+
activeSub = prevSub
|
|
794
|
+
e.flags = Watching
|
|
795
|
+
throw err
|
|
796
|
+
}
|
|
797
|
+
} else {
|
|
798
|
+
e.flags = Watching
|
|
799
|
+
}
|
|
755
800
|
} else {
|
|
756
801
|
e.flags = Watching
|
|
757
802
|
}
|
|
@@ -870,7 +915,8 @@ function signalOper<T>(this: SignalNode<T>, value?: T): T | void {
|
|
|
870
915
|
}
|
|
871
916
|
|
|
872
917
|
const flags = this.flags
|
|
873
|
-
|
|
918
|
+
// During cleanup, don't update signal - return currentValue as-is
|
|
919
|
+
if (flags & Dirty && !inCleanup) {
|
|
874
920
|
if (updateSignal(this)) {
|
|
875
921
|
const subs = this.subs
|
|
876
922
|
if (subs !== undefined) shallowPropagate(subs)
|
|
@@ -976,6 +1022,44 @@ export function effect(fn: () => void): EffectDisposer {
|
|
|
976
1022
|
|
|
977
1023
|
return effectOper.bind(e) as EffectDisposer
|
|
978
1024
|
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Create a reactive effect with a custom cleanup runner
|
|
1028
|
+
* The cleanup runner is called BEFORE signal values are committed, allowing
|
|
1029
|
+
* cleanup functions to access the previous values of signals.
|
|
1030
|
+
* @param fn - The effect function
|
|
1031
|
+
* @param cleanupRunner - Function to run cleanups before signal value commit
|
|
1032
|
+
* @returns An effect disposer function
|
|
1033
|
+
*/
|
|
1034
|
+
export function effectWithCleanup(fn: () => void, cleanupRunner: () => void): EffectDisposer {
|
|
1035
|
+
const e: EffectNode = {
|
|
1036
|
+
fn,
|
|
1037
|
+
subs: undefined,
|
|
1038
|
+
subsTail: undefined,
|
|
1039
|
+
deps: undefined,
|
|
1040
|
+
depsTail: undefined,
|
|
1041
|
+
flags: WatchingRunning,
|
|
1042
|
+
runCleanup: cleanupRunner,
|
|
1043
|
+
__id: undefined as number | undefined,
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
registerEffectDevtools(e)
|
|
1047
|
+
|
|
1048
|
+
const prevSub = activeSub
|
|
1049
|
+
if (prevSub !== undefined) link(e, prevSub, 0)
|
|
1050
|
+
activeSub = e
|
|
1051
|
+
|
|
1052
|
+
try {
|
|
1053
|
+
effectRunDevtools(e)
|
|
1054
|
+
fn()
|
|
1055
|
+
} finally {
|
|
1056
|
+
activeSub = prevSub
|
|
1057
|
+
e.flags &= ~Running
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
return effectOper.bind(e) as EffectDisposer
|
|
1061
|
+
}
|
|
1062
|
+
|
|
979
1063
|
function effectOper(this: EffectNode): void {
|
|
980
1064
|
disposeNode(this)
|
|
981
1065
|
}
|
|
@@ -1057,10 +1141,20 @@ export function endBatch(): void {
|
|
|
1057
1141
|
*/
|
|
1058
1142
|
export function batch<T>(fn: () => T): T {
|
|
1059
1143
|
++batchDepth
|
|
1144
|
+
let _error: unknown
|
|
1145
|
+
let hasError = false
|
|
1060
1146
|
try {
|
|
1061
1147
|
return fn()
|
|
1148
|
+
} catch (e) {
|
|
1149
|
+
_error = e
|
|
1150
|
+
hasError = true
|
|
1151
|
+
throw e
|
|
1062
1152
|
} finally {
|
|
1063
|
-
|
|
1153
|
+
--batchDepth
|
|
1154
|
+
// Only flush if no error occurred to avoid interfering with error propagation
|
|
1155
|
+
if (!hasError && batchDepth === 0) {
|
|
1156
|
+
flush()
|
|
1157
|
+
}
|
|
1064
1158
|
}
|
|
1065
1159
|
}
|
|
1066
1160
|
/**
|
|
@@ -1253,7 +1347,7 @@ export function createSelector<T>(
|
|
|
1253
1347
|
let current = source()
|
|
1254
1348
|
const observers = new Map<T, SignalAccessor<boolean>>()
|
|
1255
1349
|
|
|
1256
|
-
effect(() => {
|
|
1350
|
+
const dispose = effect(() => {
|
|
1257
1351
|
const next = source()
|
|
1258
1352
|
if (equalityFn(current, next)) return
|
|
1259
1353
|
|
|
@@ -1265,6 +1359,10 @@ export function createSelector<T>(
|
|
|
1265
1359
|
|
|
1266
1360
|
current = next
|
|
1267
1361
|
})
|
|
1362
|
+
registerRootCleanup(() => {
|
|
1363
|
+
dispose()
|
|
1364
|
+
observers.clear()
|
|
1365
|
+
})
|
|
1268
1366
|
|
|
1269
1367
|
return (key: T) => {
|
|
1270
1368
|
let sig = observers.get(key)
|