@actuallab/fusion-react 13.0.25 → 13.0.167
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 +70 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -7
- package/dist/index.d.ts +18 -7
- package/dist/index.js +72 -57
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -32,46 +32,48 @@ module.exports = __toCommonJS(index_exports);
|
|
|
32
32
|
var import_react = require("react");
|
|
33
33
|
var import_fusion = require("@actuallab/fusion");
|
|
34
34
|
function useComputedState(computer, deps, options) {
|
|
35
|
-
const [, forceRender] = (0, import_react.useReducer)((c) => c + 1, 0);
|
|
36
35
|
const stateRef = (0, import_react.useRef)(null);
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
while (!cancelled && !state.isDisposed) {
|
|
56
|
-
try {
|
|
57
|
-
await state.whenUpdated();
|
|
58
|
-
} catch {
|
|
59
|
-
return;
|
|
36
|
+
const generationRef = (0, import_react.useRef)(0);
|
|
37
|
+
const subscribe = (0, import_react.useCallback)(
|
|
38
|
+
(onStoreChange) => {
|
|
39
|
+
const state2 = new import_fusion.ComputedState(computer, options);
|
|
40
|
+
generationRef.current++;
|
|
41
|
+
stateRef.current = state2;
|
|
42
|
+
onStoreChange();
|
|
43
|
+
let cancelled = false;
|
|
44
|
+
void (async () => {
|
|
45
|
+
while (!cancelled && !state2.isDisposed) {
|
|
46
|
+
const sinceIndex = state2.updateIndex;
|
|
47
|
+
try {
|
|
48
|
+
await state2.whenUpdated(sinceIndex);
|
|
49
|
+
} catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!cancelled && !state2.isDisposed)
|
|
53
|
+
onStoreChange();
|
|
60
54
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
55
|
+
})();
|
|
56
|
+
return () => {
|
|
57
|
+
cancelled = true;
|
|
58
|
+
state2.dispose();
|
|
59
|
+
if (stateRef.current === state2)
|
|
60
|
+
stateRef.current = null;
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
deps
|
|
64
|
+
);
|
|
65
|
+
(0, import_react.useSyncExternalStore)(
|
|
66
|
+
subscribe,
|
|
67
|
+
() => stateRef.current === null ? "" : `${generationRef.current}:${stateRef.current.updateIndex}`,
|
|
68
|
+
() => ""
|
|
69
|
+
);
|
|
70
|
+
const state = stateRef.current ?? void 0;
|
|
71
|
+
if (state === void 0 || state.updateIndex === 0)
|
|
72
|
+
return { value: void 0, error: void 0, isInitial: true, state };
|
|
71
73
|
return {
|
|
72
|
-
value:
|
|
73
|
-
error:
|
|
74
|
-
isInitial,
|
|
74
|
+
value: state.valueOrUndefined,
|
|
75
|
+
error: state.error,
|
|
76
|
+
isInitial: false,
|
|
75
77
|
state
|
|
76
78
|
};
|
|
77
79
|
}
|
|
@@ -80,28 +82,41 @@ function useComputedState(computer, deps, options) {
|
|
|
80
82
|
var import_react2 = require("react");
|
|
81
83
|
var import_fusion2 = require("@actuallab/fusion");
|
|
82
84
|
function useMutableState(initial) {
|
|
83
|
-
const [, forceRender] = (0, import_react2.useReducer)((c) => c + 1, 0);
|
|
84
85
|
const stateRef = (0, import_react2.useRef)(null);
|
|
85
86
|
stateRef.current ??= new import_fusion2.MutableState(initial);
|
|
86
87
|
const state = stateRef.current;
|
|
87
|
-
(0, import_react2.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
const subscribe = (0, import_react2.useCallback)(
|
|
89
|
+
(onStoreChange) => {
|
|
90
|
+
let cancelled = false;
|
|
91
|
+
void (async () => {
|
|
92
|
+
while (!cancelled) {
|
|
93
|
+
const sinceIndex = state.updateIndex;
|
|
94
|
+
try {
|
|
95
|
+
await state.whenUpdated(sinceIndex);
|
|
96
|
+
} catch {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!cancelled)
|
|
100
|
+
onStoreChange();
|
|
95
101
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
})();
|
|
103
|
+
return () => {
|
|
104
|
+
cancelled = true;
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
[state]
|
|
108
|
+
);
|
|
109
|
+
(0, import_react2.useSyncExternalStore)(
|
|
110
|
+
subscribe,
|
|
111
|
+
() => state.updateIndex,
|
|
112
|
+
() => state.updateIndex
|
|
113
|
+
);
|
|
114
|
+
return {
|
|
115
|
+
value: state.valueOrUndefined,
|
|
116
|
+
error: state.error,
|
|
117
|
+
set: (value) => state.set(value),
|
|
118
|
+
state
|
|
119
|
+
};
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
// src/index.ts
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/use-computed-state.ts","../src/use-mutable-state.ts"],"sourcesContent":["export { useComputedState } from './use-computed-state.js';\r\nexport type { UseComputedStateResult } from './use-computed-state.js';\r\nexport { useMutableState } from './use-mutable-state.js';\r\n// Re-export from @actuallab/fusion for convenience\r\nexport { UIActionTracker, uiActions, UIUpdateDelayer } from '@actuallab/fusion';\r\n","import {
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/use-computed-state.ts","../src/use-mutable-state.ts"],"sourcesContent":["export { useComputedState } from './use-computed-state.js';\r\nexport type { UseComputedStateResult } from './use-computed-state.js';\r\nexport { useMutableState } from './use-mutable-state.js';\r\nexport type { UseMutableStateResult } from './use-mutable-state.js';\r\n// Re-export from @actuallab/fusion for convenience\r\nexport { UIActionTracker, uiActions, UIUpdateDelayer } from '@actuallab/fusion';\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport {\r\n ComputedState,\r\n type ComputedStateOptions,\r\n type StateComputer,\r\n} from '@actuallab/fusion';\r\n\r\nexport interface UseComputedStateResult<T> {\r\n value: T | undefined;\r\n error: unknown;\r\n isInitial: boolean;\r\n state: ComputedState<T> | undefined;\r\n}\r\n\r\n/**\r\n * React hook wrapping Fusion's ComputedState, built on useSyncExternalStore so\r\n * updates between render and subscription are never lost and rendering is tear-free.\r\n * The ComputedState is created and disposed exclusively inside `subscribe` (keyed on\r\n * deps), so a discarded concurrent render leaks nothing and a StrictMode\r\n * mount→cleanup→remount rebuilds a live state instead of freezing on a disposed one.\r\n */\r\nexport function useComputedState<T>(\r\n computer: StateComputer<T>,\r\n deps: readonly unknown[],\r\n options?: ComputedStateOptions<T>\r\n): UseComputedStateResult<T> {\r\n const stateRef = useRef<ComputedState<T> | null>(null);\r\n const generationRef = useRef(0);\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => {\r\n const state = new ComputedState<T>(computer, options);\r\n generationRef.current++;\r\n stateRef.current = state;\r\n onStoreChange();\r\n let cancelled = false;\r\n void (async () => {\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\r\n while (!cancelled && !state.isDisposed) {\r\n const sinceIndex = state.updateIndex;\r\n try {\r\n await state.whenUpdated(sinceIndex);\r\n } catch {\r\n return;\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\r\n if (!cancelled && !state.isDisposed)\r\n onStoreChange();\r\n }\r\n })();\r\n\r\n return () => {\r\n cancelled = true;\r\n state.dispose();\r\n if (stateRef.current === state)\r\n stateRef.current = null;\r\n };\r\n },\r\n deps\r\n );\r\n\r\n // Snapshot is `generation:updateIndex` — the generation makes a rebuilt state\r\n // distinct even at the same updateIndex (a sync computer reaches index 1 inside\r\n // the constructor, so a deps-change replacement would otherwise collide with the\r\n // rendered snapshot and never re-render). '' marks \"no state yet\" (the pre-effect\r\n // first render), which the built-in re-check upgrades once `subscribe` creates one.\r\n useSyncExternalStore(\r\n subscribe,\r\n () =>\r\n stateRef.current === null\r\n ? ''\r\n : `${generationRef.current}:${stateRef.current.updateIndex}`,\r\n () => ''\r\n );\r\n\r\n const state = stateRef.current ?? undefined;\r\n if (state === undefined || state.updateIndex === 0)\r\n return { value: undefined, error: undefined, isInitial: true, state };\r\n\r\n return {\r\n value: state.valueOrUndefined,\r\n error: state.error,\r\n isInitial: false,\r\n state,\r\n };\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { MutableState } from '@actuallab/fusion';\r\nimport type { Result } from '@actuallab/core';\r\n\r\nexport interface UseMutableStateResult<T> {\r\n value: T | undefined;\r\n error: unknown;\r\n set: (value: Result<T> | T) => void;\r\n state: MutableState<T>;\r\n}\r\n\r\n/**\r\n * React hook wrapping Fusion's MutableState, built on useSyncExternalStore so a\r\n * `set` landing between render and subscription is never lost. `value` is\r\n * `state.valueOrUndefined` and never throws on an error result; the error is\r\n * exposed separately.\r\n */\r\nexport function useMutableState<T>(initial: T): UseMutableStateResult<T> {\r\n const stateRef = useRef<MutableState<T> | null>(null);\r\n stateRef.current ??= new MutableState<T>(initial);\r\n const state = stateRef.current;\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => {\r\n let cancelled = false;\r\n void (async () => {\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\r\n while (!cancelled) {\r\n const sinceIndex = state.updateIndex;\r\n try {\r\n await state.whenUpdated(sinceIndex);\r\n } catch {\r\n return;\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\r\n if (!cancelled)\r\n onStoreChange();\r\n }\r\n })();\r\n\r\n return () => {\r\n cancelled = true;\r\n };\r\n },\r\n [state]\r\n );\r\n\r\n useSyncExternalStore(\r\n subscribe,\r\n () => state.updateIndex,\r\n () => state.updateIndex\r\n );\r\n\r\n return {\r\n value: state.valueOrUndefined,\r\n error: state.error,\r\n set: (value: Result<T> | T) => state.set(value),\r\n state,\r\n };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAA0D;AAC1D,oBAIO;AAgBA,SAAS,iBACZ,UACA,MACA,SACyB;AACzB,QAAM,eAAW,qBAAgC,IAAI;AACrD,QAAM,oBAAgB,qBAAO,CAAC;AAE9B,QAAM,gBAAY;AAAA,IACd,CAAC,kBAA8B;AAC3B,YAAMA,SAAQ,IAAI,4BAAiB,UAAU,OAAO;AACpD,oBAAc;AACd,eAAS,UAAUA;AACnB,oBAAc;AACd,UAAI,YAAY;AAChB,YAAM,YAAY;AAEd,eAAO,CAAC,aAAa,CAACA,OAAM,YAAY;AACpC,gBAAM,aAAaA,OAAM;AACzB,cAAI;AACA,kBAAMA,OAAM,YAAY,UAAU;AAAA,UACtC,QAAQ;AACJ;AAAA,UACJ;AAEA,cAAI,CAAC,aAAa,CAACA,OAAM;AACrB,0BAAc;AAAA,QACtB;AAAA,MACJ,GAAG;AAEH,aAAO,MAAM;AACT,oBAAY;AACZ,QAAAA,OAAM,QAAQ;AACd,YAAI,SAAS,YAAYA;AACrB,mBAAS,UAAU;AAAA,MAC3B;AAAA,IACJ;AAAA,IACA;AAAA,EACJ;AAOA;AAAA,IACI;AAAA,IACA,MACI,SAAS,YAAY,OACf,KACA,GAAG,cAAc,OAAO,IAAI,SAAS,QAAQ,WAAW;AAAA,IAClE,MAAM;AAAA,EACV;AAEA,QAAM,QAAQ,SAAS,WAAW;AAClC,MAAI,UAAU,UAAa,MAAM,gBAAgB;AAC7C,WAAO,EAAE,OAAO,QAAW,OAAO,QAAW,WAAW,MAAM,MAAM;AAExE,SAAO;AAAA,IACH,OAAO,MAAM;AAAA,IACb,OAAO,MAAM;AAAA,IACb,WAAW;AAAA,IACX;AAAA,EACJ;AACJ;;;ACrFA,IAAAC,gBAA0D;AAC1D,IAAAC,iBAA6B;AAgBtB,SAAS,gBAAmB,SAAsC;AACrE,QAAM,eAAW,sBAA+B,IAAI;AACpD,WAAS,YAAY,IAAI,4BAAgB,OAAO;AAChD,QAAM,QAAQ,SAAS;AAEvB,QAAM,gBAAY;AAAA,IACd,CAAC,kBAA8B;AAC3B,UAAI,YAAY;AAChB,YAAM,YAAY;AAEd,eAAO,CAAC,WAAW;AACf,gBAAM,aAAa,MAAM;AACzB,cAAI;AACA,kBAAM,MAAM,YAAY,UAAU;AAAA,UACtC,QAAQ;AACJ;AAAA,UACJ;AAEA,cAAI,CAAC;AACD,0BAAc;AAAA,QACtB;AAAA,MACJ,GAAG;AAEH,aAAO,MAAM;AACT,oBAAY;AAAA,MAChB;AAAA,IACJ;AAAA,IACA,CAAC,KAAK;AAAA,EACV;AAEA;AAAA,IACI;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,EAChB;AAEA,SAAO;AAAA,IACH,OAAO,MAAM;AAAA,IACb,OAAO,MAAM;AAAA,IACb,KAAK,CAAC,UAAyB,MAAM,IAAI,KAAK;AAAA,IAC9C;AAAA,EACJ;AACJ;;;AFtDA,IAAAC,iBAA4D;","names":["state","import_react","import_fusion","import_fusion"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -6,18 +6,29 @@ interface UseComputedStateResult<T> {
|
|
|
6
6
|
value: T | undefined;
|
|
7
7
|
error: unknown;
|
|
8
8
|
isInitial: boolean;
|
|
9
|
-
state: ComputedState<T
|
|
9
|
+
state: ComputedState<T> | undefined;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* React hook wrapping Fusion's ComputedState
|
|
13
|
-
*
|
|
12
|
+
* React hook wrapping Fusion's ComputedState, built on useSyncExternalStore so
|
|
13
|
+
* updates between render and subscription are never lost and rendering is tear-free.
|
|
14
|
+
* The ComputedState is created and disposed exclusively inside `subscribe` (keyed on
|
|
15
|
+
* deps), so a discarded concurrent render leaks nothing and a StrictMode
|
|
16
|
+
* mount→cleanup→remount rebuilds a live state instead of freezing on a disposed one.
|
|
14
17
|
*/
|
|
15
18
|
declare function useComputedState<T>(computer: StateComputer<T>, deps: readonly unknown[], options?: ComputedStateOptions<T>): UseComputedStateResult<T>;
|
|
16
19
|
|
|
20
|
+
interface UseMutableStateResult<T> {
|
|
21
|
+
value: T | undefined;
|
|
22
|
+
error: unknown;
|
|
23
|
+
set: (value: Result<T> | T) => void;
|
|
24
|
+
state: MutableState<T>;
|
|
25
|
+
}
|
|
17
26
|
/**
|
|
18
|
-
* React hook wrapping Fusion's MutableState
|
|
19
|
-
*
|
|
27
|
+
* React hook wrapping Fusion's MutableState, built on useSyncExternalStore so a
|
|
28
|
+
* `set` landing between render and subscription is never lost. `value` is
|
|
29
|
+
* `state.valueOrUndefined` and never throws on an error result; the error is
|
|
30
|
+
* exposed separately.
|
|
20
31
|
*/
|
|
21
|
-
declare function useMutableState<T>(initial: T):
|
|
32
|
+
declare function useMutableState<T>(initial: T): UseMutableStateResult<T>;
|
|
22
33
|
|
|
23
|
-
export { type UseComputedStateResult, useComputedState, useMutableState };
|
|
34
|
+
export { type UseComputedStateResult, type UseMutableStateResult, useComputedState, useMutableState };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,18 +6,29 @@ interface UseComputedStateResult<T> {
|
|
|
6
6
|
value: T | undefined;
|
|
7
7
|
error: unknown;
|
|
8
8
|
isInitial: boolean;
|
|
9
|
-
state: ComputedState<T
|
|
9
|
+
state: ComputedState<T> | undefined;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* React hook wrapping Fusion's ComputedState
|
|
13
|
-
*
|
|
12
|
+
* React hook wrapping Fusion's ComputedState, built on useSyncExternalStore so
|
|
13
|
+
* updates between render and subscription are never lost and rendering is tear-free.
|
|
14
|
+
* The ComputedState is created and disposed exclusively inside `subscribe` (keyed on
|
|
15
|
+
* deps), so a discarded concurrent render leaks nothing and a StrictMode
|
|
16
|
+
* mount→cleanup→remount rebuilds a live state instead of freezing on a disposed one.
|
|
14
17
|
*/
|
|
15
18
|
declare function useComputedState<T>(computer: StateComputer<T>, deps: readonly unknown[], options?: ComputedStateOptions<T>): UseComputedStateResult<T>;
|
|
16
19
|
|
|
20
|
+
interface UseMutableStateResult<T> {
|
|
21
|
+
value: T | undefined;
|
|
22
|
+
error: unknown;
|
|
23
|
+
set: (value: Result<T> | T) => void;
|
|
24
|
+
state: MutableState<T>;
|
|
25
|
+
}
|
|
17
26
|
/**
|
|
18
|
-
* React hook wrapping Fusion's MutableState
|
|
19
|
-
*
|
|
27
|
+
* React hook wrapping Fusion's MutableState, built on useSyncExternalStore so a
|
|
28
|
+
* `set` landing between render and subscription is never lost. `value` is
|
|
29
|
+
* `state.valueOrUndefined` and never throws on an error result; the error is
|
|
30
|
+
* exposed separately.
|
|
20
31
|
*/
|
|
21
|
-
declare function useMutableState<T>(initial: T):
|
|
32
|
+
declare function useMutableState<T>(initial: T): UseMutableStateResult<T>;
|
|
22
33
|
|
|
23
|
-
export { type UseComputedStateResult, useComputedState, useMutableState };
|
|
34
|
+
export { type UseComputedStateResult, type UseMutableStateResult, useComputedState, useMutableState };
|
package/dist/index.js
CHANGED
|
@@ -1,79 +1,94 @@
|
|
|
1
1
|
// src/use-computed-state.ts
|
|
2
|
-
import {
|
|
2
|
+
import { useCallback, useRef, useSyncExternalStore } from "react";
|
|
3
3
|
import {
|
|
4
4
|
ComputedState
|
|
5
5
|
} from "@actuallab/fusion";
|
|
6
6
|
function useComputedState(computer, deps, options) {
|
|
7
|
-
const [, forceRender] = useReducer((c) => c + 1, 0);
|
|
8
7
|
const stateRef = useRef(null);
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
while (!cancelled && !state.isDisposed) {
|
|
28
|
-
try {
|
|
29
|
-
await state.whenUpdated();
|
|
30
|
-
} catch {
|
|
31
|
-
return;
|
|
8
|
+
const generationRef = useRef(0);
|
|
9
|
+
const subscribe = useCallback(
|
|
10
|
+
(onStoreChange) => {
|
|
11
|
+
const state2 = new ComputedState(computer, options);
|
|
12
|
+
generationRef.current++;
|
|
13
|
+
stateRef.current = state2;
|
|
14
|
+
onStoreChange();
|
|
15
|
+
let cancelled = false;
|
|
16
|
+
void (async () => {
|
|
17
|
+
while (!cancelled && !state2.isDisposed) {
|
|
18
|
+
const sinceIndex = state2.updateIndex;
|
|
19
|
+
try {
|
|
20
|
+
await state2.whenUpdated(sinceIndex);
|
|
21
|
+
} catch {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!cancelled && !state2.isDisposed)
|
|
25
|
+
onStoreChange();
|
|
32
26
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
})();
|
|
28
|
+
return () => {
|
|
29
|
+
cancelled = true;
|
|
30
|
+
state2.dispose();
|
|
31
|
+
if (stateRef.current === state2)
|
|
32
|
+
stateRef.current = null;
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
deps
|
|
36
|
+
);
|
|
37
|
+
useSyncExternalStore(
|
|
38
|
+
subscribe,
|
|
39
|
+
() => stateRef.current === null ? "" : `${generationRef.current}:${stateRef.current.updateIndex}`,
|
|
40
|
+
() => ""
|
|
41
|
+
);
|
|
42
|
+
const state = stateRef.current ?? void 0;
|
|
43
|
+
if (state === void 0 || state.updateIndex === 0)
|
|
44
|
+
return { value: void 0, error: void 0, isInitial: true, state };
|
|
43
45
|
return {
|
|
44
|
-
value:
|
|
45
|
-
error:
|
|
46
|
-
isInitial,
|
|
46
|
+
value: state.valueOrUndefined,
|
|
47
|
+
error: state.error,
|
|
48
|
+
isInitial: false,
|
|
47
49
|
state
|
|
48
50
|
};
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
// src/use-mutable-state.ts
|
|
52
|
-
import {
|
|
54
|
+
import { useCallback as useCallback2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
53
55
|
import { MutableState } from "@actuallab/fusion";
|
|
54
56
|
function useMutableState(initial) {
|
|
55
|
-
const [, forceRender] = useReducer2((c) => c + 1, 0);
|
|
56
57
|
const stateRef = useRef2(null);
|
|
57
58
|
stateRef.current ??= new MutableState(initial);
|
|
58
59
|
const state = stateRef.current;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
const subscribe = useCallback2(
|
|
61
|
+
(onStoreChange) => {
|
|
62
|
+
let cancelled = false;
|
|
63
|
+
void (async () => {
|
|
64
|
+
while (!cancelled) {
|
|
65
|
+
const sinceIndex = state.updateIndex;
|
|
66
|
+
try {
|
|
67
|
+
await state.whenUpdated(sinceIndex);
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!cancelled)
|
|
72
|
+
onStoreChange();
|
|
67
73
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
})();
|
|
75
|
+
return () => {
|
|
76
|
+
cancelled = true;
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
[state]
|
|
80
|
+
);
|
|
81
|
+
useSyncExternalStore2(
|
|
82
|
+
subscribe,
|
|
83
|
+
() => state.updateIndex,
|
|
84
|
+
() => state.updateIndex
|
|
85
|
+
);
|
|
86
|
+
return {
|
|
87
|
+
value: state.valueOrUndefined,
|
|
88
|
+
error: state.error,
|
|
89
|
+
set: (value) => state.set(value),
|
|
90
|
+
state
|
|
91
|
+
};
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
// src/index.ts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/use-computed-state.ts","../src/use-mutable-state.ts","../src/index.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../src/use-computed-state.ts","../src/use-mutable-state.ts","../src/index.ts"],"sourcesContent":["import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport {\r\n ComputedState,\r\n type ComputedStateOptions,\r\n type StateComputer,\r\n} from '@actuallab/fusion';\r\n\r\nexport interface UseComputedStateResult<T> {\r\n value: T | undefined;\r\n error: unknown;\r\n isInitial: boolean;\r\n state: ComputedState<T> | undefined;\r\n}\r\n\r\n/**\r\n * React hook wrapping Fusion's ComputedState, built on useSyncExternalStore so\r\n * updates between render and subscription are never lost and rendering is tear-free.\r\n * The ComputedState is created and disposed exclusively inside `subscribe` (keyed on\r\n * deps), so a discarded concurrent render leaks nothing and a StrictMode\r\n * mount→cleanup→remount rebuilds a live state instead of freezing on a disposed one.\r\n */\r\nexport function useComputedState<T>(\r\n computer: StateComputer<T>,\r\n deps: readonly unknown[],\r\n options?: ComputedStateOptions<T>\r\n): UseComputedStateResult<T> {\r\n const stateRef = useRef<ComputedState<T> | null>(null);\r\n const generationRef = useRef(0);\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => {\r\n const state = new ComputedState<T>(computer, options);\r\n generationRef.current++;\r\n stateRef.current = state;\r\n onStoreChange();\r\n let cancelled = false;\r\n void (async () => {\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\r\n while (!cancelled && !state.isDisposed) {\r\n const sinceIndex = state.updateIndex;\r\n try {\r\n await state.whenUpdated(sinceIndex);\r\n } catch {\r\n return;\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\r\n if (!cancelled && !state.isDisposed)\r\n onStoreChange();\r\n }\r\n })();\r\n\r\n return () => {\r\n cancelled = true;\r\n state.dispose();\r\n if (stateRef.current === state)\r\n stateRef.current = null;\r\n };\r\n },\r\n deps\r\n );\r\n\r\n // Snapshot is `generation:updateIndex` — the generation makes a rebuilt state\r\n // distinct even at the same updateIndex (a sync computer reaches index 1 inside\r\n // the constructor, so a deps-change replacement would otherwise collide with the\r\n // rendered snapshot and never re-render). '' marks \"no state yet\" (the pre-effect\r\n // first render), which the built-in re-check upgrades once `subscribe` creates one.\r\n useSyncExternalStore(\r\n subscribe,\r\n () =>\r\n stateRef.current === null\r\n ? ''\r\n : `${generationRef.current}:${stateRef.current.updateIndex}`,\r\n () => ''\r\n );\r\n\r\n const state = stateRef.current ?? undefined;\r\n if (state === undefined || state.updateIndex === 0)\r\n return { value: undefined, error: undefined, isInitial: true, state };\r\n\r\n return {\r\n value: state.valueOrUndefined,\r\n error: state.error,\r\n isInitial: false,\r\n state,\r\n };\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { MutableState } from '@actuallab/fusion';\r\nimport type { Result } from '@actuallab/core';\r\n\r\nexport interface UseMutableStateResult<T> {\r\n value: T | undefined;\r\n error: unknown;\r\n set: (value: Result<T> | T) => void;\r\n state: MutableState<T>;\r\n}\r\n\r\n/**\r\n * React hook wrapping Fusion's MutableState, built on useSyncExternalStore so a\r\n * `set` landing between render and subscription is never lost. `value` is\r\n * `state.valueOrUndefined` and never throws on an error result; the error is\r\n * exposed separately.\r\n */\r\nexport function useMutableState<T>(initial: T): UseMutableStateResult<T> {\r\n const stateRef = useRef<MutableState<T> | null>(null);\r\n stateRef.current ??= new MutableState<T>(initial);\r\n const state = stateRef.current;\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => {\r\n let cancelled = false;\r\n void (async () => {\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\r\n while (!cancelled) {\r\n const sinceIndex = state.updateIndex;\r\n try {\r\n await state.whenUpdated(sinceIndex);\r\n } catch {\r\n return;\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\r\n if (!cancelled)\r\n onStoreChange();\r\n }\r\n })();\r\n\r\n return () => {\r\n cancelled = true;\r\n };\r\n },\r\n [state]\r\n );\r\n\r\n useSyncExternalStore(\r\n subscribe,\r\n () => state.updateIndex,\r\n () => state.updateIndex\r\n );\r\n\r\n return {\r\n value: state.valueOrUndefined,\r\n error: state.error,\r\n set: (value: Result<T> | T) => state.set(value),\r\n state,\r\n };\r\n}\r\n","export { useComputedState } from './use-computed-state.js';\r\nexport type { UseComputedStateResult } from './use-computed-state.js';\r\nexport { useMutableState } from './use-mutable-state.js';\r\nexport type { UseMutableStateResult } from './use-mutable-state.js';\r\n// Re-export from @actuallab/fusion for convenience\r\nexport { UIActionTracker, uiActions, UIUpdateDelayer } from '@actuallab/fusion';\r\n"],"mappings":";AAAA,SAAS,aAAa,QAAQ,4BAA4B;AAC1D;AAAA,EACI;AAAA,OAGG;AAgBA,SAAS,iBACZ,UACA,MACA,SACyB;AACzB,QAAM,WAAW,OAAgC,IAAI;AACrD,QAAM,gBAAgB,OAAO,CAAC;AAE9B,QAAM,YAAY;AAAA,IACd,CAAC,kBAA8B;AAC3B,YAAMA,SAAQ,IAAI,cAAiB,UAAU,OAAO;AACpD,oBAAc;AACd,eAAS,UAAUA;AACnB,oBAAc;AACd,UAAI,YAAY;AAChB,YAAM,YAAY;AAEd,eAAO,CAAC,aAAa,CAACA,OAAM,YAAY;AACpC,gBAAM,aAAaA,OAAM;AACzB,cAAI;AACA,kBAAMA,OAAM,YAAY,UAAU;AAAA,UACtC,QAAQ;AACJ;AAAA,UACJ;AAEA,cAAI,CAAC,aAAa,CAACA,OAAM;AACrB,0BAAc;AAAA,QACtB;AAAA,MACJ,GAAG;AAEH,aAAO,MAAM;AACT,oBAAY;AACZ,QAAAA,OAAM,QAAQ;AACd,YAAI,SAAS,YAAYA;AACrB,mBAAS,UAAU;AAAA,MAC3B;AAAA,IACJ;AAAA,IACA;AAAA,EACJ;AAOA;AAAA,IACI;AAAA,IACA,MACI,SAAS,YAAY,OACf,KACA,GAAG,cAAc,OAAO,IAAI,SAAS,QAAQ,WAAW;AAAA,IAClE,MAAM;AAAA,EACV;AAEA,QAAM,QAAQ,SAAS,WAAW;AAClC,MAAI,UAAU,UAAa,MAAM,gBAAgB;AAC7C,WAAO,EAAE,OAAO,QAAW,OAAO,QAAW,WAAW,MAAM,MAAM;AAExE,SAAO;AAAA,IACH,OAAO,MAAM;AAAA,IACb,OAAO,MAAM;AAAA,IACb,WAAW;AAAA,IACX;AAAA,EACJ;AACJ;;;ACrFA,SAAS,eAAAC,cAAa,UAAAC,SAAQ,wBAAAC,6BAA4B;AAC1D,SAAS,oBAAoB;AAgBtB,SAAS,gBAAmB,SAAsC;AACrE,QAAM,WAAWD,QAA+B,IAAI;AACpD,WAAS,YAAY,IAAI,aAAgB,OAAO;AAChD,QAAM,QAAQ,SAAS;AAEvB,QAAM,YAAYD;AAAA,IACd,CAAC,kBAA8B;AAC3B,UAAI,YAAY;AAChB,YAAM,YAAY;AAEd,eAAO,CAAC,WAAW;AACf,gBAAM,aAAa,MAAM;AACzB,cAAI;AACA,kBAAM,MAAM,YAAY,UAAU;AAAA,UACtC,QAAQ;AACJ;AAAA,UACJ;AAEA,cAAI,CAAC;AACD,0BAAc;AAAA,QACtB;AAAA,MACJ,GAAG;AAEH,aAAO,MAAM;AACT,oBAAY;AAAA,MAChB;AAAA,IACJ;AAAA,IACA,CAAC,KAAK;AAAA,EACV;AAEA,EAAAE;AAAA,IACI;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,EAChB;AAEA,SAAO;AAAA,IACH,OAAO,MAAM;AAAA,IACb,OAAO,MAAM;AAAA,IACb,KAAK,CAAC,UAAyB,MAAM,IAAI,KAAK;AAAA,IAC9C;AAAA,EACJ;AACJ;;;ACtDA,SAAS,iBAAiB,WAAW,uBAAuB;","names":["state","useCallback","useRef","useSyncExternalStore"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuallab/fusion-react",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.167",
|
|
4
4
|
"description": "React integration for Fusion - hooks and UI action tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"clean": "rm -rf dist"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@actuallab/core": "13.0.
|
|
30
|
-
"@actuallab/fusion": "13.0.
|
|
29
|
+
"@actuallab/core": "13.0.167",
|
|
30
|
+
"@actuallab/fusion": "13.0.167"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"react": "^19.0.0"
|