@kuindji/reactive 1.0.23 → 1.1.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 +34 -11
- package/dist/action.d.ts +12 -10
- package/dist/action.js +23 -16
- package/dist/actionBus.d.ts +8 -4
- package/dist/actionBus.js +37 -8
- package/dist/actionMap.d.ts +21 -19
- package/dist/actionMap.js +12 -9
- package/dist/event.d.ts +3 -2
- package/dist/event.js +160 -109
- package/dist/eventBus.d.ts +5 -3
- package/dist/eventBus.js +158 -104
- package/dist/index.d.ts +7 -7
- package/dist/index.js +7 -23
- package/dist/lib/actionMapInternal.d.ts +8 -0
- package/dist/lib/actionMapInternal.js +8 -0
- package/dist/lib/asyncCall.js +1 -4
- package/dist/lib/isPromiseLike.d.ts +1 -0
- package/dist/lib/isPromiseLike.js +5 -0
- package/dist/lib/listenerSorter.js +1 -4
- package/dist/lib/normalizeEventOptions.d.ts +13 -0
- package/dist/lib/normalizeEventOptions.js +21 -0
- package/dist/lib/tagsIntersect.js +1 -4
- package/dist/lib/types.js +4 -7
- package/dist/react/ErrorBoundary.d.ts +1 -1
- package/dist/react/ErrorBoundary.js +10 -13
- package/dist/react/listenerOptionsEqual.d.ts +27 -0
- package/dist/react/listenerOptionsEqual.js +121 -0
- package/dist/react/useAction.d.ts +3 -3
- package/dist/react/useAction.js +25 -25
- package/dist/react/useActionBus.d.ts +4 -4
- package/dist/react/useActionBus.js +41 -14
- package/dist/react/useActionMap.d.ts +4 -4
- package/dist/react/useActionMap.js +46 -16
- package/dist/react/useEvent.d.ts +2 -2
- package/dist/react/useEvent.js +30 -17
- package/dist/react/useEventBus.d.ts +2 -2
- package/dist/react/useEventBus.js +27 -26
- package/dist/react/useListenToAction.d.ts +1 -1
- package/dist/react/useListenToAction.js +24 -48
- package/dist/react/useListenToActionBus.d.ts +3 -3
- package/dist/react/useListenToActionBus.js +22 -19
- package/dist/react/useListenToEvent.d.ts +2 -2
- package/dist/react/useListenToEvent.js +13 -14
- package/dist/react/useListenToEventBus.d.ts +3 -3
- package/dist/react/useListenToEventBus.js +14 -15
- package/dist/react/useListenToStoreChanges.d.ts +3 -3
- package/dist/react/useListenToStoreChanges.js +12 -13
- package/dist/react/useReconciledListener.d.ts +33 -0
- package/dist/react/useReconciledListener.js +44 -0
- package/dist/react/useStore.d.ts +2 -2
- package/dist/react/useStore.js +72 -23
- package/dist/react/useStoreState.d.ts +2 -2
- package/dist/react/useStoreState.js +16 -26
- package/dist/react.d.ts +13 -13
- package/dist/react.js +13 -29
- package/dist/store.d.ts +9 -8
- package/dist/store.js +116 -53
- package/package.json +13 -3
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const eventBus = (0, react_1.useMemo)(() => {
|
|
14
|
-
const eventBus = (0, eventBus_1.createEventBus)(eventBusOptions);
|
|
1
|
+
import { useContext, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { createEventBus, } from "../eventBus.js";
|
|
3
|
+
import { ErrorBoundaryContext } from "./ErrorBoundary.js";
|
|
4
|
+
import { areEventBusOptionsEqual } from "./listenerOptionsEqual.js";
|
|
5
|
+
export function useEventBus(eventBusOptions, allEventsListener, errorListener) {
|
|
6
|
+
const boundaryErrorListener = useContext(ErrorBoundaryContext);
|
|
7
|
+
const committedOptionsRef = useRef(eventBusOptions);
|
|
8
|
+
const errorListenerRef = useRef(errorListener || null);
|
|
9
|
+
const allEventsListenerRef = useRef(allEventsListener || null);
|
|
10
|
+
const boundaryErrorListenerRef = useRef(boundaryErrorListener || null);
|
|
11
|
+
const eventBus = useMemo(() => {
|
|
12
|
+
const eventBus = createEventBus(eventBusOptions);
|
|
15
13
|
if (allEventsListener) {
|
|
16
14
|
eventBus.addAllEventsListener(allEventsListener);
|
|
17
15
|
}
|
|
@@ -23,15 +21,19 @@ function useEventBus(eventBusOptions, allEventsListener, errorListener) {
|
|
|
23
21
|
}
|
|
24
22
|
return eventBus;
|
|
25
23
|
}, []);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
// Reconcile event bus options across renders instead of throwing. Present
|
|
25
|
+
// entries are applied via event.setOptions; a removed event-name entry
|
|
26
|
+
// leaves the existing event unchanged.
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (committedOptionsRef.current === eventBusOptions) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!areEventBusOptionsEqual(committedOptionsRef.current, eventBusOptions)) {
|
|
32
|
+
eventBus.setOptions(eventBusOptions);
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
committedOptionsRef.current = eventBusOptions;
|
|
35
|
+
});
|
|
36
|
+
useEffect(() => {
|
|
35
37
|
if (allEventsListenerRef.current !== allEventsListener) {
|
|
36
38
|
if (allEventsListenerRef.current) {
|
|
37
39
|
eventBus.removeAllEventsListener(allEventsListenerRef.current);
|
|
@@ -42,7 +44,7 @@ function useEventBus(eventBusOptions, allEventsListener, errorListener) {
|
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
}, [allEventsListener]);
|
|
45
|
-
|
|
47
|
+
useEffect(() => {
|
|
46
48
|
if (errorListenerRef.current !== errorListener) {
|
|
47
49
|
if (errorListenerRef.current) {
|
|
48
50
|
eventBus.removeErrorListener(errorListenerRef.current);
|
|
@@ -53,7 +55,7 @@ function useEventBus(eventBusOptions, allEventsListener, errorListener) {
|
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
}, [errorListener]);
|
|
56
|
-
|
|
58
|
+
useEffect(() => {
|
|
57
59
|
if (boundaryErrorListenerRef.current !== boundaryErrorListener) {
|
|
58
60
|
if (boundaryErrorListenerRef.current) {
|
|
59
61
|
eventBus.removeErrorListener(boundaryErrorListenerRef.current);
|
|
@@ -65,7 +67,7 @@ function useEventBus(eventBusOptions, allEventsListener, errorListener) {
|
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
}, [boundaryErrorListener]);
|
|
68
|
-
|
|
70
|
+
useEffect(() => {
|
|
69
71
|
return () => {
|
|
70
72
|
if (allEventsListenerRef.current) {
|
|
71
73
|
eventBus.removeAllEventsListener(allEventsListenerRef.current);
|
|
@@ -79,7 +81,6 @@ function useEventBus(eventBusOptions, allEventsListener, errorListener) {
|
|
|
79
81
|
eventBus.removeErrorListener(boundaryErrorListenerRef.current);
|
|
80
82
|
boundaryErrorListenerRef.current = null;
|
|
81
83
|
}
|
|
82
|
-
updateRef.current = 0;
|
|
83
84
|
};
|
|
84
85
|
}, []);
|
|
85
86
|
return eventBus;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { BaseAction } from "../action";
|
|
1
|
+
import type { BaseAction } from "../action.js";
|
|
2
2
|
export type { BaseAction };
|
|
3
3
|
export declare function useListenToAction<TAction extends BaseAction, TListenerSignature extends TAction["__type"]["listenerSignature"] = TAction["__type"]["listenerSignature"], TErrorListenerSignature extends TAction["__type"]["errorListenerSignature"] = TAction["__type"]["errorListenerSignature"], TBeforeActionListenerSignature extends TAction["__type"]["beforeActionSignature"] = TAction["__type"]["beforeActionSignature"]>(action: TAction, listener: TListenerSignature | null, errorListener?: TErrorListenerSignature | null, beforeActionListener?: TBeforeActionListenerSignature | null): void;
|
|
@@ -1,55 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const react_1 = require("react");
|
|
5
|
-
function useListenToAction(action, listener, errorListener, beforeActionListener) {
|
|
6
|
-
const listenerRef = (0, react_1.useRef)(listener);
|
|
7
|
-
const actionRef = (0, react_1.useRef)(action);
|
|
8
|
-
const errorListenerRef = (0, react_1.useRef)(null);
|
|
9
|
-
const beforeActionListenerRef = (0, react_1.useRef)(null);
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
export function useListenToAction(action, listener, errorListener, beforeActionListener) {
|
|
3
|
+
const listenerRef = useRef(listener);
|
|
10
4
|
listenerRef.current = listener;
|
|
11
|
-
const genericHandler =
|
|
5
|
+
const genericHandler = useCallback((arg) => {
|
|
12
6
|
var _a;
|
|
13
7
|
(_a = listenerRef.current) === null || _a === void 0 ? void 0 : _a.call(listenerRef, arg);
|
|
14
8
|
}, []);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
actionRef.current = action;
|
|
18
|
-
actionRef.current.addListener(genericHandler);
|
|
19
|
-
}, [action]);
|
|
20
|
-
(0, react_1.useEffect)(() => {
|
|
21
|
-
if (errorListenerRef.current !== errorListener) {
|
|
22
|
-
if (errorListenerRef.current) {
|
|
23
|
-
actionRef.current.removeErrorListener(errorListenerRef.current);
|
|
24
|
-
}
|
|
25
|
-
errorListenerRef.current = errorListener || null;
|
|
26
|
-
if (errorListener) {
|
|
27
|
-
actionRef.current.addErrorListener(errorListener);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}, [errorListener]);
|
|
31
|
-
(0, react_1.useEffect)(() => {
|
|
32
|
-
if (beforeActionListenerRef.current !== beforeActionListener) {
|
|
33
|
-
if (beforeActionListenerRef.current) {
|
|
34
|
-
actionRef.current.removeBeforeActionListener(beforeActionListenerRef.current);
|
|
35
|
-
}
|
|
36
|
-
beforeActionListenerRef.current = beforeActionListener || null;
|
|
37
|
-
if (beforeActionListener) {
|
|
38
|
-
actionRef.current.addBeforeActionListener(beforeActionListener);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}, [beforeActionListener]);
|
|
42
|
-
(0, react_1.useEffect)(() => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
action.addListener(genericHandler);
|
|
43
11
|
return () => {
|
|
44
|
-
|
|
45
|
-
if (errorListenerRef.current) {
|
|
46
|
-
actionRef.current.removeErrorListener(errorListenerRef.current);
|
|
47
|
-
errorListenerRef.current = null;
|
|
48
|
-
}
|
|
49
|
-
if (beforeActionListenerRef.current) {
|
|
50
|
-
actionRef.current.removeBeforeActionListener(beforeActionListenerRef.current);
|
|
51
|
-
beforeActionListenerRef.current = null;
|
|
52
|
-
}
|
|
12
|
+
action.removeListener(genericHandler);
|
|
53
13
|
};
|
|
54
|
-
}, []);
|
|
14
|
+
}, [action, genericHandler]);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (errorListener) {
|
|
17
|
+
action.addErrorListener(errorListener);
|
|
18
|
+
return () => {
|
|
19
|
+
action.removeErrorListener(errorListener);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}, [action, errorListener]);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (beforeActionListener) {
|
|
25
|
+
action.addBeforeActionListener(beforeActionListener);
|
|
26
|
+
return () => {
|
|
27
|
+
action.removeBeforeActionListener(beforeActionListener);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}, [action, beforeActionListener]);
|
|
55
31
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { BaseActionBus } from "../actionBus";
|
|
2
|
-
import type { ListenerOptions } from "../event";
|
|
3
|
-
import type { ErrorListenerSignature, KeyOf } from "../lib/types";
|
|
1
|
+
import type { BaseActionBus } from "../actionBus.js";
|
|
2
|
+
import type { ListenerOptions } from "../event.js";
|
|
3
|
+
import type { ErrorListenerSignature, KeyOf } from "../lib/types.js";
|
|
4
4
|
export type { BaseActionBus, ErrorListenerSignature, ListenerOptions };
|
|
5
5
|
export declare function useListenToActionBus<TActionBus extends BaseActionBus, TKey extends KeyOf<TActionBus["__type"]["actions"]>, TListener extends TActionBus["__type"]["actions"][TKey]["listenerSignature"], TBeforeActionListener extends TActionBus["__type"]["actions"][TKey]["beforeActionSignature"]>(actionBus: TActionBus, actionName: TKey, listener?: TListener | null | {
|
|
6
6
|
listener?: TListener;
|
|
@@ -1,37 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const react_1 = require("react");
|
|
5
|
-
function useListenToActionBus(actionBus, actionName, listener, options, errorListener, beforeActionListener) {
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { useReconciledListener } from "./useReconciledListener.js";
|
|
3
|
+
export function useListenToActionBus(actionBus, actionName, listener, options, errorListener, beforeActionListener) {
|
|
6
4
|
if (listener && typeof listener !== "function") {
|
|
7
5
|
options = listener.options;
|
|
8
6
|
errorListener = listener.errorListener;
|
|
9
7
|
beforeActionListener = listener.beforeActionListener;
|
|
10
8
|
listener = listener.listener;
|
|
11
9
|
}
|
|
12
|
-
const listenerRef =
|
|
13
|
-
const beforeActionListenerRef =
|
|
10
|
+
const listenerRef = useRef(listener || null);
|
|
11
|
+
const beforeActionListenerRef = useRef(null);
|
|
14
12
|
listenerRef.current = listener || null;
|
|
15
13
|
beforeActionListenerRef.current = beforeActionListener || null;
|
|
16
|
-
const genericHandler =
|
|
14
|
+
const genericHandler = useCallback((arg) => {
|
|
17
15
|
var _a;
|
|
18
16
|
return (_a = listenerRef.current) === null || _a === void 0 ? void 0 : _a.call(listenerRef, arg);
|
|
19
17
|
}, []);
|
|
20
|
-
const genericBeforeActionHandler =
|
|
18
|
+
const genericBeforeActionHandler = useCallback((...args) => {
|
|
21
19
|
var _a;
|
|
22
|
-
return (
|
|
20
|
+
return (_a = beforeActionListenerRef.current) === null || _a === void 0 ? void 0 : _a.call(beforeActionListenerRef, ...args);
|
|
23
21
|
}, []);
|
|
24
|
-
// Main listener + beforeAction listener -
|
|
25
|
-
(
|
|
26
|
-
actionBus
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
actionBus.
|
|
22
|
+
// Main listener + beforeAction listener - reconciled across changes
|
|
23
|
+
useReconciledListener({
|
|
24
|
+
keyDeps: [actionBus, actionName],
|
|
25
|
+
options: options !== null && options !== void 0 ? options : undefined,
|
|
26
|
+
subscribe: (opts) => {
|
|
27
|
+
actionBus.addListener(actionName, genericHandler, opts !== null && opts !== void 0 ? opts : undefined);
|
|
28
|
+
actionBus.get(actionName).addBeforeActionListener(genericBeforeActionHandler);
|
|
29
|
+
},
|
|
30
|
+
unsubscribe: (ctx) => {
|
|
31
|
+
actionBus.removeListener(actionName, genericHandler, ctx);
|
|
30
32
|
actionBus.get(actionName).removeBeforeActionListener(genericBeforeActionHandler);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
+
},
|
|
34
|
+
update: (ctx, opts) => actionBus.updateListenerOptions(actionName, genericHandler, ctx, opts !== null && opts !== void 0 ? opts : undefined),
|
|
35
|
+
});
|
|
33
36
|
// Error listener - bus level
|
|
34
|
-
|
|
37
|
+
useEffect(() => {
|
|
35
38
|
if (errorListener) {
|
|
36
39
|
actionBus.addErrorListener(errorListener);
|
|
37
40
|
return () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaseEvent, ListenerOptions } from "../event";
|
|
2
|
-
import type { ErrorListenerSignature } from "../lib/types";
|
|
1
|
+
import type { BaseEvent, ListenerOptions } from "../event.js";
|
|
2
|
+
import type { ErrorListenerSignature } from "../lib/types.js";
|
|
3
3
|
export type { BaseEvent, ErrorListenerSignature, ListenerOptions };
|
|
4
4
|
export declare function useListenToEvent<TEvent extends BaseEvent, TListenerSignature extends TEvent["__type"]["signature"], TErrorListenerSignature extends TEvent["__type"]["errorListenerSignature"]>(event: TEvent, listener: TListenerSignature, options?: ListenerOptions, errorListener?: TErrorListenerSignature): void;
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
function useListenToEvent(event, listener, options, errorListener) {
|
|
6
|
-
const listenerRef = (0, react_1.useRef)(listener);
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { useReconciledListener } from "./useReconciledListener.js";
|
|
3
|
+
export function useListenToEvent(event, listener, options, errorListener) {
|
|
4
|
+
const listenerRef = useRef(listener);
|
|
7
5
|
listenerRef.current = listener;
|
|
8
|
-
const genericHandler =
|
|
6
|
+
const genericHandler = useCallback((...args) => {
|
|
9
7
|
return listenerRef.current(...args);
|
|
10
8
|
}, []);
|
|
11
|
-
(
|
|
12
|
-
event
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
useReconciledListener({
|
|
10
|
+
keyDeps: [event],
|
|
11
|
+
options,
|
|
12
|
+
subscribe: (opts) => event.addListener(genericHandler, opts !== null && opts !== void 0 ? opts : undefined),
|
|
13
|
+
unsubscribe: (ctx) => event.removeListener(genericHandler, ctx),
|
|
14
|
+
update: (ctx, opts) => event.updateListenerOptions(genericHandler, ctx, opts !== null && opts !== void 0 ? opts : undefined),
|
|
15
|
+
});
|
|
16
|
+
useEffect(() => {
|
|
18
17
|
if (errorListener) {
|
|
19
18
|
event.addErrorListener(errorListener);
|
|
20
19
|
return () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ListenerOptions } from "../event";
|
|
2
|
-
import type { BaseEventBus } from "../eventBus";
|
|
3
|
-
import type { ErrorListenerSignature, KeyOf } from "../lib/types";
|
|
1
|
+
import type { ListenerOptions } from "../event.js";
|
|
2
|
+
import type { BaseEventBus } from "../eventBus.js";
|
|
3
|
+
import type { ErrorListenerSignature, KeyOf } from "../lib/types.js";
|
|
4
4
|
export type { BaseEventBus, ErrorListenerSignature, ListenerOptions };
|
|
5
5
|
export declare function useListenToEventBus<TEventBus extends BaseEventBus, TKey extends KeyOf<TEventBus["__type"]["eventSignatures"]>, TListener extends TEventBus["__type"]["eventSignatures"][TKey]>(eventBus: TEventBus, eventName: TKey, listener: TListener, options?: ListenerOptions, errorListener?: ErrorListenerSignature<any[]>): void;
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
function useListenToEventBus(eventBus, eventName, listener, options, errorListener) {
|
|
6
|
-
const listenerRef = (0, react_1.useRef)(listener);
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { useReconciledListener } from "./useReconciledListener.js";
|
|
3
|
+
export function useListenToEventBus(eventBus, eventName, listener, options, errorListener) {
|
|
4
|
+
const listenerRef = useRef(listener);
|
|
7
5
|
listenerRef.current = listener;
|
|
8
|
-
const genericHandler =
|
|
6
|
+
const genericHandler = useCallback((...args) => {
|
|
9
7
|
var _a;
|
|
10
8
|
return (_a = listenerRef.current) === null || _a === void 0 ? void 0 : _a.call(listenerRef, ...args);
|
|
11
9
|
}, []);
|
|
12
|
-
// Main listener -
|
|
13
|
-
(
|
|
14
|
-
eventBus
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
// Main listener - reconciled across eventBus/eventName/option changes
|
|
11
|
+
useReconciledListener({
|
|
12
|
+
keyDeps: [eventBus, eventName],
|
|
13
|
+
options,
|
|
14
|
+
subscribe: (opts) => eventBus.addListener(eventName, genericHandler, opts !== null && opts !== void 0 ? opts : undefined),
|
|
15
|
+
unsubscribe: (ctx) => eventBus.removeListener(eventName, genericHandler, ctx),
|
|
16
|
+
update: (ctx, opts) => eventBus.updateListenerOptions(eventName, genericHandler, ctx, opts !== null && opts !== void 0 ? opts : undefined),
|
|
17
|
+
});
|
|
19
18
|
// Error listener - cleanup pattern
|
|
20
|
-
|
|
19
|
+
useEffect(() => {
|
|
21
20
|
if (errorListener) {
|
|
22
21
|
eventBus.addErrorListener(errorListener);
|
|
23
22
|
return () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ListenerOptions } from "../event";
|
|
2
|
-
import { KeyOf } from "../lib/types";
|
|
3
|
-
import type { BaseStore } from "../store";
|
|
1
|
+
import type { ListenerOptions } from "../event.js";
|
|
2
|
+
import { KeyOf } from "../lib/types.js";
|
|
3
|
+
import type { BaseStore } from "../store.js";
|
|
4
4
|
export type { BaseStore, ListenerOptions };
|
|
5
5
|
export declare function useListenToStoreChanges<TStore extends BaseStore, TKey extends KeyOf<TStore["__type"]["propTypes"]>, TListener extends TStore["__type"]["changeEvents"][TKey]>(store: TStore, key: TKey, listener: TListener, options?: ListenerOptions): void;
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
function useListenToStoreChanges(store, key, listener, options) {
|
|
6
|
-
const listenerRef = (0, react_1.useRef)(listener);
|
|
1
|
+
import { useCallback, useRef } from "react";
|
|
2
|
+
import { useReconciledListener } from "./useReconciledListener.js";
|
|
3
|
+
export function useListenToStoreChanges(store, key, listener, options) {
|
|
4
|
+
const listenerRef = useRef(listener);
|
|
7
5
|
listenerRef.current = listener;
|
|
8
|
-
const genericHandler =
|
|
6
|
+
const genericHandler = useCallback((value, previousValue) => {
|
|
9
7
|
return listenerRef.current(value, previousValue);
|
|
10
8
|
}, []);
|
|
11
|
-
(
|
|
12
|
-
store
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
useReconciledListener({
|
|
10
|
+
keyDeps: [store, key],
|
|
11
|
+
options,
|
|
12
|
+
subscribe: (opts) => store.onChange(key, genericHandler, opts !== null && opts !== void 0 ? opts : undefined),
|
|
13
|
+
unsubscribe: (ctx) => store.removeOnChange(key, genericHandler, ctx),
|
|
14
|
+
update: (ctx, opts) => store.updateOnChangeOptions(key, genericHandler, ctx, opts !== null && opts !== void 0 ? opts : undefined),
|
|
15
|
+
});
|
|
17
16
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ListenerOptions } from "../event.js";
|
|
2
|
+
type ListenerOps = {
|
|
3
|
+
/**
|
|
4
|
+
* Identity dependencies. When any element changes (reference equality) the
|
|
5
|
+
* listener is fully resubscribed: the old registration is removed using the
|
|
6
|
+
* previous closure (previous target + previous context) and a fresh one is
|
|
7
|
+
* added. `context` is appended automatically because it is part of listener
|
|
8
|
+
* identity. The array length must stay constant across renders.
|
|
9
|
+
*/
|
|
10
|
+
keyDeps: ReadonlyArray<unknown>;
|
|
11
|
+
options?: ListenerOptions | null;
|
|
12
|
+
/** Add the listener to the current target with the given options. */
|
|
13
|
+
subscribe: (options?: ListenerOptions | null) => void;
|
|
14
|
+
/** Remove the listener from the current target using the given context. */
|
|
15
|
+
unsubscribe: (context: object | null) => void;
|
|
16
|
+
/** Update soft options on the live listener in place (counters preserved). */
|
|
17
|
+
update: (context: object | null, options?: ListenerOptions | null) => void;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Reconciles a single reactive listener across renders without relying on the
|
|
21
|
+
* identity of the options object.
|
|
22
|
+
*
|
|
23
|
+
* Two effects cooperate:
|
|
24
|
+
* - an identity effect keyed by `[...keyDeps, context]` performs the classic
|
|
25
|
+
* add-on-mount / remove-on-cleanup cycle, so target/context changes (and
|
|
26
|
+
* React StrictMode remounts) resubscribe correctly using the OLD context on
|
|
27
|
+
* cleanup;
|
|
28
|
+
* - a reconciliation effect runs every render and, when only soft options
|
|
29
|
+
* changed, updates the live listener in place instead of resubscribing, so
|
|
30
|
+
* per-listener counters are preserved.
|
|
31
|
+
*/
|
|
32
|
+
export declare function useReconciledListener({ keyDeps, options, subscribe, unsubscribe, update, }: ListenerOps): void;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { areListenerOptionsEqual } from "./listenerOptionsEqual.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reconciles a single reactive listener across renders without relying on the
|
|
5
|
+
* identity of the options object.
|
|
6
|
+
*
|
|
7
|
+
* Two effects cooperate:
|
|
8
|
+
* - an identity effect keyed by `[...keyDeps, context]` performs the classic
|
|
9
|
+
* add-on-mount / remove-on-cleanup cycle, so target/context changes (and
|
|
10
|
+
* React StrictMode remounts) resubscribe correctly using the OLD context on
|
|
11
|
+
* cleanup;
|
|
12
|
+
* - a reconciliation effect runs every render and, when only soft options
|
|
13
|
+
* changed, updates the live listener in place instead of resubscribing, so
|
|
14
|
+
* per-listener counters are preserved.
|
|
15
|
+
*/
|
|
16
|
+
export function useReconciledListener({ keyDeps, options, subscribe, unsubscribe, update, }) {
|
|
17
|
+
var _a;
|
|
18
|
+
const context = (_a = options === null || options === void 0 ? void 0 : options.context) !== null && _a !== void 0 ? _a : null;
|
|
19
|
+
const committedRef = useRef(undefined);
|
|
20
|
+
const registeredRef = useRef(false);
|
|
21
|
+
// Identity effect: (re)subscribe on target/context change.
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
subscribe(options);
|
|
24
|
+
committedRef.current = options;
|
|
25
|
+
registeredRef.current = true;
|
|
26
|
+
return () => {
|
|
27
|
+
unsubscribe(context);
|
|
28
|
+
registeredRef.current = false;
|
|
29
|
+
};
|
|
30
|
+
}, [...keyDeps, context]);
|
|
31
|
+
// Reconciliation effect: in-place soft-option updates every render.
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!registeredRef.current) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (committedRef.current === options) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!areListenerOptionsEqual(committedRef.current, options)) {
|
|
40
|
+
update(context, options);
|
|
41
|
+
}
|
|
42
|
+
committedRef.current = options;
|
|
43
|
+
});
|
|
44
|
+
}
|
package/dist/react/useStore.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createStore } from "../store";
|
|
2
|
-
import type { BasePropMap, BeforeChangeEventName, ChangeEventName, ErrorEventName, ResetEventName, StoreDefinitionHelper } from "../store";
|
|
1
|
+
import { createStore } from "../store.js";
|
|
2
|
+
import type { BasePropMap, BeforeChangeEventName, ChangeEventName, ErrorEventName, ResetEventName, StoreDefinitionHelper } from "../store.js";
|
|
3
3
|
export type { BasePropMap, BeforeChangeEventName, ChangeEventName, ErrorEventName, ResetEventName, StoreDefinitionHelper, };
|
|
4
4
|
export declare function useStore<PropMap extends BasePropMap, Store extends StoreDefinitionHelper<PropMap> = StoreDefinitionHelper<PropMap>, Config extends {
|
|
5
5
|
onChange?: Partial<Store["changeEvents"]>;
|
package/dist/react/useStore.js
CHANGED
|
@@ -1,29 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { createStore } from "../store.js";
|
|
3
|
+
export function useStore(initialData = {}, config) {
|
|
4
|
+
// initialData is seed-only (captured once); later changes are ignored.
|
|
5
|
+
const store = useMemo(() => createStore(initialData), []);
|
|
6
|
+
// Track only the handlers we added (per category + key) and compare by
|
|
7
|
+
// reference, so consumer listeners added outside the hook are never
|
|
8
|
+
// touched and inline-equal config maps do not duplicate or churn
|
|
9
|
+
// subscriptions.
|
|
10
|
+
const appliedRef = useRef({
|
|
11
|
+
onChange: {},
|
|
12
|
+
pipes: {},
|
|
13
|
+
control: {},
|
|
14
|
+
});
|
|
15
|
+
const add = (category, key, fn) => {
|
|
16
|
+
if (category === "onChange") {
|
|
17
|
+
store.onChange(key, fn);
|
|
13
18
|
}
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
// @ts-expect-error - TS widens for-in key to string; types are correct
|
|
17
|
-
store.pipe(key, config.pipes[key]);
|
|
18
|
-
}
|
|
19
|
+
else if (category === "pipes") {
|
|
20
|
+
store.pipe(key, fn);
|
|
19
21
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
else {
|
|
23
|
+
store.control(key, fn);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const remove = (category, key, fn) => {
|
|
27
|
+
if (category === "onChange") {
|
|
28
|
+
store.removeOnChange(key, fn);
|
|
25
29
|
}
|
|
26
|
-
|
|
30
|
+
else if (category === "pipes") {
|
|
31
|
+
store.removePipe(key, fn);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
store.removeControl(key, fn);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
// Reconcile config handlers every render (no cleanup here, so equal config
|
|
38
|
+
// never causes remove/add churn).
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const categories = {
|
|
41
|
+
onChange: config === null || config === void 0 ? void 0 : config.onChange,
|
|
42
|
+
pipes: config === null || config === void 0 ? void 0 : config.pipes,
|
|
43
|
+
control: config === null || config === void 0 ? void 0 : config.control,
|
|
44
|
+
};
|
|
45
|
+
Object.keys(categories).forEach((category) => {
|
|
46
|
+
var _a;
|
|
47
|
+
const next = (_a = categories[category]) !== null && _a !== void 0 ? _a : {};
|
|
48
|
+
const prev = appliedRef.current[category];
|
|
49
|
+
// Remove stale/changed handlers before adding (matters for pipes).
|
|
50
|
+
for (const key in prev) {
|
|
51
|
+
if (next[key] !== prev[key]) {
|
|
52
|
+
remove(category, key, prev[key]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const key in next) {
|
|
56
|
+
if (next[key] !== prev[key]) {
|
|
57
|
+
add(category, key, next[key]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
appliedRef.current[category] = Object.assign({}, next);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
// Unmount cleanup: detach everything we applied (also makes StrictMode
|
|
64
|
+
// remount re-subscribe cleanly via the reconcile effect above).
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
return () => {
|
|
67
|
+
const applied = appliedRef.current;
|
|
68
|
+
Object.keys(applied).forEach((category) => {
|
|
69
|
+
const map = applied[category];
|
|
70
|
+
for (const key in map) {
|
|
71
|
+
remove(category, key, map[key]);
|
|
72
|
+
}
|
|
73
|
+
applied[category] = {};
|
|
74
|
+
});
|
|
75
|
+
};
|
|
27
76
|
}, []);
|
|
28
77
|
return store;
|
|
29
78
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { KeyOf } from "../lib/types";
|
|
2
|
-
import { BaseStore } from "../store";
|
|
1
|
+
import { KeyOf } from "../lib/types.js";
|
|
2
|
+
import { BaseStore } from "../store.js";
|
|
3
3
|
export declare function useStoreState<TStore extends BaseStore, TKey extends KeyOf<TStore["__type"]["propTypes"]>>(store: TStore, key: TKey): readonly [TStore["__type"]["propTypes"][TKey], (value: TStore["__type"]["propTypes"][TKey] | ((previousValue?: TStore["__type"]["propTypes"][TKey]) => TStore["__type"]["propTypes"][TKey])) => void];
|
|
@@ -1,33 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}, []);
|
|
12
|
-
const
|
|
1
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
2
|
+
export function useStoreState(store, key) {
|
|
3
|
+
const subscribe = useCallback((onStoreChange) => {
|
|
4
|
+
const listener = () => {
|
|
5
|
+
onStoreChange();
|
|
6
|
+
};
|
|
7
|
+
store.onChange(key, listener);
|
|
8
|
+
return () => {
|
|
9
|
+
store.removeOnChange(key, listener);
|
|
10
|
+
};
|
|
11
|
+
}, [store, key]);
|
|
12
|
+
const getSnapshot = useCallback(() => store.get(key), [store, key]);
|
|
13
|
+
const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
14
|
+
const setter = useCallback((value) => {
|
|
13
15
|
if (typeof value === "function") {
|
|
14
|
-
|
|
16
|
+
store.set(key, value(store.get(key)));
|
|
15
17
|
}
|
|
16
18
|
else {
|
|
17
|
-
|
|
19
|
+
store.set(key, value);
|
|
18
20
|
}
|
|
19
|
-
}, []);
|
|
20
|
-
(0, react_1.useEffect)(() => {
|
|
21
|
-
return () => {
|
|
22
|
-
storeRef.current.removeOnChange(keyRef.current, onChange);
|
|
23
|
-
};
|
|
24
|
-
}, []);
|
|
25
|
-
(0, react_1.useEffect)(() => {
|
|
26
|
-
storeRef.current.removeOnChange(keyRef.current, onChange);
|
|
27
|
-
storeRef.current = store;
|
|
28
|
-
keyRef.current = key;
|
|
29
|
-
storeRef.current.onChange(keyRef.current, onChange);
|
|
30
|
-
setValue(store.get(key));
|
|
31
21
|
}, [store, key]);
|
|
32
22
|
return [value, setter];
|
|
33
23
|
}
|