@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.
Files changed (58) hide show
  1. package/README.md +34 -11
  2. package/dist/action.d.ts +12 -10
  3. package/dist/action.js +23 -16
  4. package/dist/actionBus.d.ts +8 -4
  5. package/dist/actionBus.js +37 -8
  6. package/dist/actionMap.d.ts +21 -19
  7. package/dist/actionMap.js +12 -9
  8. package/dist/event.d.ts +3 -2
  9. package/dist/event.js +160 -109
  10. package/dist/eventBus.d.ts +5 -3
  11. package/dist/eventBus.js +158 -104
  12. package/dist/index.d.ts +7 -7
  13. package/dist/index.js +7 -23
  14. package/dist/lib/actionMapInternal.d.ts +8 -0
  15. package/dist/lib/actionMapInternal.js +8 -0
  16. package/dist/lib/asyncCall.js +1 -4
  17. package/dist/lib/isPromiseLike.d.ts +1 -0
  18. package/dist/lib/isPromiseLike.js +5 -0
  19. package/dist/lib/listenerSorter.js +1 -4
  20. package/dist/lib/normalizeEventOptions.d.ts +13 -0
  21. package/dist/lib/normalizeEventOptions.js +21 -0
  22. package/dist/lib/tagsIntersect.js +1 -4
  23. package/dist/lib/types.js +4 -7
  24. package/dist/react/ErrorBoundary.d.ts +1 -1
  25. package/dist/react/ErrorBoundary.js +10 -13
  26. package/dist/react/listenerOptionsEqual.d.ts +27 -0
  27. package/dist/react/listenerOptionsEqual.js +121 -0
  28. package/dist/react/useAction.d.ts +3 -3
  29. package/dist/react/useAction.js +25 -25
  30. package/dist/react/useActionBus.d.ts +4 -4
  31. package/dist/react/useActionBus.js +41 -14
  32. package/dist/react/useActionMap.d.ts +4 -4
  33. package/dist/react/useActionMap.js +46 -16
  34. package/dist/react/useEvent.d.ts +2 -2
  35. package/dist/react/useEvent.js +30 -17
  36. package/dist/react/useEventBus.d.ts +2 -2
  37. package/dist/react/useEventBus.js +27 -26
  38. package/dist/react/useListenToAction.d.ts +1 -1
  39. package/dist/react/useListenToAction.js +24 -48
  40. package/dist/react/useListenToActionBus.d.ts +3 -3
  41. package/dist/react/useListenToActionBus.js +22 -19
  42. package/dist/react/useListenToEvent.d.ts +2 -2
  43. package/dist/react/useListenToEvent.js +13 -14
  44. package/dist/react/useListenToEventBus.d.ts +3 -3
  45. package/dist/react/useListenToEventBus.js +14 -15
  46. package/dist/react/useListenToStoreChanges.d.ts +3 -3
  47. package/dist/react/useListenToStoreChanges.js +12 -13
  48. package/dist/react/useReconciledListener.d.ts +33 -0
  49. package/dist/react/useReconciledListener.js +44 -0
  50. package/dist/react/useStore.d.ts +2 -2
  51. package/dist/react/useStore.js +72 -23
  52. package/dist/react/useStoreState.d.ts +2 -2
  53. package/dist/react/useStoreState.js +16 -26
  54. package/dist/react.d.ts +13 -13
  55. package/dist/react.js +13 -29
  56. package/dist/store.d.ts +9 -8
  57. package/dist/store.js +116 -53
  58. package/package.json +13 -3
@@ -1,17 +1,15 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useEventBus = useEventBus;
4
- const react_1 = require("react");
5
- const eventBus_1 = require("../eventBus");
6
- const ErrorBoundary_1 = require("./ErrorBoundary");
7
- function useEventBus(eventBusOptions, allEventsListener, errorListener) {
8
- const boundaryErrorListener = (0, react_1.useContext)(ErrorBoundary_1.ErrorBoundaryContext);
9
- const updateRef = (0, react_1.useRef)(0);
10
- const errorListenerRef = (0, react_1.useRef)(errorListener || null);
11
- const allEventsListenerRef = (0, react_1.useRef)(allEventsListener || null);
12
- const boundaryErrorListenerRef = (0, react_1.useRef)(boundaryErrorListener || null);
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
- (0, react_1.useEffect)(() => {
27
- if (eventBusOptions) {
28
- if (updateRef.current > 0) {
29
- throw new Error("EventBus options can't be updated");
30
- }
31
- updateRef.current++;
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
- }, [eventBusOptions]);
34
- (0, react_1.useEffect)(() => {
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
- (0, react_1.useEffect)(() => {
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
- (0, react_1.useEffect)(() => {
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
- (0, react_1.useEffect)(() => {
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
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useListenToAction = useListenToAction;
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 = (0, react_1.useCallback)((arg) => {
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
- (0, react_1.useEffect)(() => {
16
- actionRef.current.removeListener(genericHandler);
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
- actionRef.current.removeListener(genericHandler);
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
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useListenToActionBus = useListenToActionBus;
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 = (0, react_1.useRef)(listener || null);
13
- const beforeActionListenerRef = (0, react_1.useRef)(null);
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 = (0, react_1.useCallback)((arg) => {
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 = (0, react_1.useCallback)((...args) => {
18
+ const genericBeforeActionHandler = useCallback((...args) => {
21
19
  var _a;
22
- return ((_a = beforeActionListenerRef.current) === null || _a === void 0 ? void 0 : _a.call(beforeActionListenerRef, ...args)) || undefined;
20
+ return (_a = beforeActionListenerRef.current) === null || _a === void 0 ? void 0 : _a.call(beforeActionListenerRef, ...args);
23
21
  }, []);
24
- // Main listener + beforeAction listener - tied to actionName
25
- (0, react_1.useEffect)(() => {
26
- actionBus.addListener(actionName, genericHandler, options || undefined);
27
- actionBus.get(actionName).addBeforeActionListener(genericBeforeActionHandler);
28
- return () => {
29
- actionBus.removeListener(actionName, genericHandler);
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
- }, [actionBus, actionName, genericHandler, genericBeforeActionHandler]);
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
- (0, react_1.useEffect)(() => {
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
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useListenToEvent = useListenToEvent;
4
- const react_1 = require("react");
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 = (0, react_1.useCallback)((...args) => {
6
+ const genericHandler = useCallback((...args) => {
9
7
  return listenerRef.current(...args);
10
8
  }, []);
11
- (0, react_1.useEffect)(() => {
12
- event.addListener(genericHandler, options);
13
- return () => {
14
- event.removeListener(genericHandler);
15
- };
16
- }, [event, genericHandler]);
17
- (0, react_1.useEffect)(() => {
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
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useListenToEventBus = useListenToEventBus;
4
- const react_1 = require("react");
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 = (0, react_1.useCallback)((...args) => {
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 - cleanup pattern handles eventBus/eventName changes
13
- (0, react_1.useEffect)(() => {
14
- eventBus.addListener(eventName, genericHandler, options);
15
- return () => {
16
- eventBus.removeListener(eventName, genericHandler);
17
- };
18
- }, [eventBus, eventName, genericHandler]);
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
- (0, react_1.useEffect)(() => {
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
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useListenToStoreChanges = useListenToStoreChanges;
4
- const react_1 = require("react");
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 = (0, react_1.useCallback)((value, previousValue) => {
6
+ const genericHandler = useCallback((value, previousValue) => {
9
7
  return listenerRef.current(value, previousValue);
10
8
  }, []);
11
- (0, react_1.useEffect)(() => {
12
- store.onChange(key, genericHandler, options);
13
- return () => {
14
- store.removeOnChange(key, genericHandler);
15
- };
16
- }, [store, key, genericHandler]);
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
+ }
@@ -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"]>;
@@ -1,29 +1,78 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useStore = useStore;
4
- const react_1 = require("react");
5
- const store_1 = require("../store");
6
- function useStore(initialData = {}, config) {
7
- const store = (0, react_1.useMemo)(() => {
8
- const store = (0, store_1.createStore)(initialData);
9
- if (config === null || config === void 0 ? void 0 : config.onChange) {
10
- for (const key in config.onChange) {
11
- store.onChange(key, config.onChange[key]);
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 (config === null || config === void 0 ? void 0 : config.pipes) {
15
- for (const key in config.pipes) {
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
- if (config === null || config === void 0 ? void 0 : config.control) {
21
- for (const key in config.control) {
22
- // @ts-expect-error - TS widens for-in key to string; types are correct
23
- store.control(key, config.control[key]);
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
- return store;
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
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useStoreState = useStoreState;
4
- const react_1 = require("react");
5
- function useStoreState(store, key) {
6
- const [value, setValue] = (0, react_1.useState)(store.get(key));
7
- const storeRef = (0, react_1.useRef)(store);
8
- const keyRef = (0, react_1.useRef)(key);
9
- const onChange = (0, react_1.useCallback)((value) => {
10
- setValue(value);
11
- }, []);
12
- const setter = (0, react_1.useCallback)((value) => {
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
- storeRef.current.set(keyRef.current, value(storeRef.current.get(keyRef.current)));
16
+ store.set(key, value(store.get(key)));
15
17
  }
16
18
  else {
17
- storeRef.current.set(keyRef.current, value);
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
  }