cogsbox-state 0.5.472 → 0.5.473

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 (45) hide show
  1. package/dist/CogsState.d.ts +98 -82
  2. package/dist/CogsState.d.ts.map +1 -1
  3. package/dist/CogsState.jsx +1020 -950
  4. package/dist/CogsState.jsx.map +1 -1
  5. package/dist/Components.d.ts.map +1 -1
  6. package/dist/Components.jsx +276 -217
  7. package/dist/Components.jsx.map +1 -1
  8. package/dist/PluginRunner.d.ts +10 -0
  9. package/dist/PluginRunner.d.ts.map +1 -0
  10. package/dist/PluginRunner.jsx +128 -0
  11. package/dist/PluginRunner.jsx.map +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +33 -26
  15. package/dist/index.js.map +1 -1
  16. package/dist/pluginStore.d.ts +43 -0
  17. package/dist/pluginStore.d.ts.map +1 -0
  18. package/dist/pluginStore.js +52 -0
  19. package/dist/pluginStore.js.map +1 -0
  20. package/dist/plugins.d.ts +1326 -0
  21. package/dist/plugins.d.ts.map +1 -0
  22. package/dist/plugins.js +76 -0
  23. package/dist/plugins.js.map +1 -0
  24. package/dist/store.d.ts +50 -15
  25. package/dist/store.d.ts.map +1 -1
  26. package/dist/store.js +509 -470
  27. package/dist/store.js.map +1 -1
  28. package/dist/utility.d.ts +1 -1
  29. package/dist/utility.d.ts.map +1 -1
  30. package/dist/utility.js +12 -12
  31. package/dist/utility.js.map +1 -1
  32. package/dist/validation.d.ts +7 -0
  33. package/dist/validation.d.ts.map +1 -0
  34. package/dist/validation.js +39 -0
  35. package/dist/validation.js.map +1 -0
  36. package/package.json +13 -3
  37. package/src/CogsState.tsx +658 -457
  38. package/src/Components.tsx +272 -194
  39. package/src/PluginRunner.tsx +208 -0
  40. package/src/index.ts +2 -0
  41. package/src/pluginStore.ts +159 -0
  42. package/src/plugins.ts +548 -0
  43. package/src/store.ts +748 -493
  44. package/src/utility.ts +31 -31
  45. package/src/validation.ts +84 -0
@@ -0,0 +1,208 @@
1
+ import React, { useEffect, useMemo, useState, useRef, useReducer } from 'react';
2
+ import { pluginStore } from './pluginStore';
3
+ import { isDeepEqual } from './utility';
4
+ import { createMetadataContext, toDeconstructedMethods } from './plugins';
5
+ import type { CogsPlugin } from './plugins';
6
+ import type { StateObject, UpdateTypeDetail } from './CogsState';
7
+ import { FormEventType } from './store';
8
+
9
+ const { setHookResult, removeHookResult } = pluginStore.getState();
10
+
11
+ const PluginInstance = React.memo(
12
+ ({
13
+ stateKey,
14
+ plugin,
15
+ options,
16
+ stateHandler,
17
+ }: {
18
+ stateKey: string;
19
+ plugin: CogsPlugin<any, any, any, any, any>;
20
+ options: any;
21
+ stateHandler: StateObject<any>;
22
+ }) => {
23
+ const [isInitialMount, setIsInitialMount] = useState(true);
24
+ const metadataContext = useMemo(
25
+ () => createMetadataContext(stateKey, plugin.name),
26
+ [stateKey, plugin.name]
27
+ );
28
+
29
+ const deconstructed = useMemo(
30
+ () => toDeconstructedMethods(stateHandler),
31
+ [stateHandler]
32
+ );
33
+
34
+ const hookContext = useMemo(
35
+ () => ({
36
+ stateKey,
37
+ pluginName: plugin.name,
38
+ isInitialMount,
39
+ options,
40
+ ...deconstructed,
41
+ ...metadataContext,
42
+ }),
43
+ [
44
+ stateKey,
45
+ plugin.name,
46
+ isInitialMount,
47
+ options,
48
+ deconstructed,
49
+ metadataContext,
50
+ ]
51
+ );
52
+
53
+ const hookData = plugin.useHook ? plugin.useHook(hookContext) : undefined;
54
+
55
+ useEffect(() => {
56
+ setIsInitialMount(false);
57
+ }, []);
58
+
59
+ useEffect(() => {
60
+ if (plugin.useHook) setHookResult(stateKey, plugin.name, hookData);
61
+ else removeHookResult(stateKey, plugin.name);
62
+ return () => removeHookResult(stateKey, plugin.name);
63
+ }, [stateKey, plugin.name, !!plugin.useHook, hookData]);
64
+
65
+ const lastProcessedOptionsRef = useRef<any>();
66
+ const [isInitialTransform, setIsInitialTransform] = useState(true);
67
+
68
+ useEffect(() => {
69
+ if (plugin.transformState) {
70
+ if (!isDeepEqual(options, lastProcessedOptionsRef.current)) {
71
+ plugin.transformState({
72
+ stateKey,
73
+ pluginName: plugin.name,
74
+ options,
75
+ hookData,
76
+ isInitialTransform,
77
+ ...deconstructed,
78
+ ...metadataContext,
79
+ });
80
+ lastProcessedOptionsRef.current = options;
81
+ setIsInitialTransform(false);
82
+ }
83
+ }
84
+ }, [
85
+ stateKey,
86
+ plugin,
87
+ options,
88
+ hookData,
89
+ isInitialTransform,
90
+ deconstructed,
91
+ metadataContext,
92
+ ]);
93
+
94
+ const hookDataRef = useRef(hookData);
95
+ hookDataRef.current = hookData;
96
+
97
+ useEffect(() => {
98
+ if (!plugin.onUpdate) return;
99
+
100
+ const handleUpdate = (update: UpdateTypeDetail) => {
101
+ if (update.stateKey === stateKey) {
102
+ plugin.onUpdate!({
103
+ stateKey,
104
+ pluginName: plugin.name,
105
+ update,
106
+ path: update.path,
107
+ options,
108
+ hookData: hookDataRef.current,
109
+ ...deconstructed,
110
+ ...metadataContext,
111
+ });
112
+ }
113
+ };
114
+
115
+ const unsubscribe = pluginStore
116
+ .getState()
117
+ .subscribeToUpdates(handleUpdate);
118
+ return unsubscribe;
119
+ }, [stateKey, plugin, options, deconstructed, metadataContext]);
120
+
121
+ useEffect(() => {
122
+ if (!plugin.onFormUpdate) return;
123
+
124
+ const handleFormUpdate = (
125
+ event: FormEventType & { stateKey: string }
126
+ ) => {
127
+ if (event.stateKey === stateKey) {
128
+ const path = event.path;
129
+ plugin.onFormUpdate!({
130
+ stateKey,
131
+ pluginName: plugin.name,
132
+ path,
133
+ event: {
134
+ type: event.type,
135
+ value: event.value,
136
+ path,
137
+ },
138
+ options,
139
+ hookData: hookDataRef.current,
140
+ ...deconstructed,
141
+ ...metadataContext,
142
+ });
143
+ }
144
+ };
145
+
146
+ const unsubscribe = pluginStore
147
+ .getState()
148
+ .subscribeToFormUpdates(handleFormUpdate);
149
+ return unsubscribe;
150
+ }, [stateKey, plugin, options, deconstructed, metadataContext]);
151
+
152
+ return null;
153
+ }
154
+ );
155
+ /**
156
+ * The main orchestrator component. It reads from the central pluginStore
157
+ * and renders a `PluginInstance` controller for each active plugin.
158
+ */
159
+ export function PluginRunner({ children }: { children: React.ReactNode }) {
160
+ // A simple way to force a re-render when the store changes.
161
+ const [, forceUpdate] = useReducer((c) => c + 1, 0);
162
+
163
+ // Subscribe to the store. When plugins or their options are added/removed,
164
+ // this component will re-render to update the list of PluginInstances.
165
+ useEffect(() => {
166
+ const unsubscribe = pluginStore.subscribe(forceUpdate);
167
+ return unsubscribe;
168
+ }, []);
169
+
170
+ const { pluginOptions, stateHandlers, registeredPlugins } =
171
+ pluginStore.getState();
172
+
173
+ return (
174
+ <>
175
+ {/*
176
+ This declarative mapping is the core of the solution.
177
+ React will now manage adding and removing `PluginInstance` components
178
+ as the application state changes, ensuring hooks are handled safely.
179
+ */}
180
+ {Array.from(pluginOptions.entries()).map(([stateKey, pluginMap]) => {
181
+ const stateHandler = stateHandlers.get(stateKey);
182
+ if (!stateHandler) {
183
+ return null; // Don't render a runner if the state handler isn't ready.
184
+ }
185
+
186
+ return Array.from(pluginMap.entries()).map(([pluginName, options]) => {
187
+ const plugin = registeredPlugins.find((p) => p.name === pluginName);
188
+ if (!plugin) {
189
+ return null; // Don't render if the plugin is not in the registered list.
190
+ }
191
+
192
+ // Render a dedicated, memoized controller for this specific plugin configuration.
193
+ return (
194
+ <PluginInstance
195
+ key={`${stateKey}:${pluginName}`}
196
+ stateKey={stateKey}
197
+ plugin={plugin}
198
+ options={options}
199
+ stateHandler={stateHandler}
200
+ />
201
+ );
202
+ });
203
+ })}
204
+
205
+ {children}
206
+ </>
207
+ );
208
+ }
package/src/index.ts CHANGED
@@ -3,3 +3,5 @@ export * from './CogsStateClient.js';
3
3
  export * from './utility.js';
4
4
  export * from './TRPCValidationLink.js';
5
5
  export * from './store.js';
6
+ export * from './plugins.js';
7
+ export * from './PluginRunner.js';
@@ -0,0 +1,159 @@
1
+ import { create } from 'zustand';
2
+ import type { PluginData, StateObject, UpdateTypeDetail } from './CogsState';
3
+ import type { CogsPlugin } from './plugins';
4
+
5
+ type PluginRegistryStore = {
6
+ stateHandlers: Map<string, StateObject<any>>; // stateKey -> handler
7
+ registerStateHandler: (stateKey: string, handler: StateObject<any>) => void;
8
+ registeredPlugins: readonly CogsPlugin<any, any, any, any, any>[];
9
+ setRegisteredPlugins: (
10
+ plugins: readonly CogsPlugin<any, any, any, any, any>[]
11
+ ) => void;
12
+
13
+ // Store options keyed by stateKey and pluginName
14
+ pluginOptions: Map<string, Map<string, any>>; // stateKey -> pluginName -> options
15
+ setPluginOptionsForState: (
16
+ stateKey: string,
17
+ pluginOptions: Record<string, any>
18
+ ) => void;
19
+
20
+ // Get all plugin configs for a specific stateKey
21
+ getPluginConfigsForState: (stateKey: string) => Array<{
22
+ plugin: CogsPlugin<any, any, any, any, any>;
23
+ options: any;
24
+ }>;
25
+ updateSubscribers: Set<(update: UpdateTypeDetail) => void>;
26
+ subscribeToUpdates: (
27
+ callback: (update: UpdateTypeDetail) => void
28
+ ) => () => void;
29
+ notifyUpdate: (update: UpdateTypeDetail) => void;
30
+ formUpdateSubscribers: Set<
31
+ (event: {
32
+ stateKey: string;
33
+ type: 'focus' | 'blur' | 'input';
34
+ path: string[];
35
+ value?: any;
36
+ }) => void
37
+ >;
38
+ subscribeToFormUpdates: (
39
+ callback: (event: {
40
+ stateKey: string;
41
+ type: 'focus' | 'blur' | 'input';
42
+ path: string[];
43
+ value?: any;
44
+ }) => void
45
+ ) => () => void;
46
+ notifyFormUpdate: (event: {
47
+ stateKey: string;
48
+ type: 'focus' | 'blur' | 'input';
49
+ path: string[];
50
+ value?: any;
51
+ }) => void;
52
+ hookResults: Map<string, Map<string, any>>; // stateKey -> pluginName -> hook
53
+ setHookResult: (stateKey: string, pluginName: string, data: any) => void;
54
+ getHookResult: (stateKey: string, pluginName: string) => any | undefined;
55
+ removeHookResult: (stateKey: string, pluginName: string) => void;
56
+ };
57
+
58
+ export const pluginStore = create<PluginRegistryStore>((set, get) => ({
59
+ stateHandlers: new Map(),
60
+ registerStateHandler: (stateKey, handler) =>
61
+ set((state) => {
62
+ const newMap = new Map(state.stateHandlers);
63
+ newMap.set(stateKey, handler);
64
+ console.log('addign handler', stateKey, handler);
65
+ return { stateHandlers: newMap };
66
+ }),
67
+ registeredPlugins: [],
68
+ pluginOptions: new Map(),
69
+
70
+ setRegisteredPlugins: (plugins) => set({ registeredPlugins: plugins }),
71
+
72
+ setPluginOptionsForState: (stateKey, pluginOptions) =>
73
+ set((state) => {
74
+ const newMap = new Map(state.pluginOptions);
75
+ const statePluginMap = new Map();
76
+
77
+ // Store each plugin's options
78
+ Object.entries(pluginOptions).forEach(([pluginName, options]) => {
79
+ // Only store if this is actually a registered plugin
80
+ if (state.registeredPlugins.some((p) => p.name === pluginName)) {
81
+ statePluginMap.set(pluginName, options);
82
+ }
83
+ });
84
+
85
+ if (statePluginMap.size > 0) {
86
+ newMap.set(stateKey, statePluginMap);
87
+ }
88
+
89
+ return { pluginOptions: newMap };
90
+ }),
91
+
92
+ getPluginConfigsForState: (stateKey) => {
93
+ const state = get();
94
+ const stateOptions = state.pluginOptions.get(stateKey);
95
+ if (!stateOptions) return [];
96
+
97
+ return state.registeredPlugins
98
+ .map((plugin) => {
99
+ const options = stateOptions.get(plugin.name);
100
+ if (options !== undefined) {
101
+ return { plugin, options };
102
+ }
103
+ return null;
104
+ })
105
+ .filter(Boolean) as Array<{
106
+ plugin: CogsPlugin<any, any, any, any, any>;
107
+ options: any;
108
+ }>;
109
+ },
110
+ updateSubscribers: new Set(),
111
+ subscribeToUpdates: (callback) => {
112
+ const subscribers = get().updateSubscribers;
113
+ subscribers.add(callback);
114
+ // Return an unsubscribe function
115
+ return () => {
116
+ get().updateSubscribers.delete(callback);
117
+ };
118
+ },
119
+ notifyUpdate: (update) => {
120
+ // Call all registered subscribers with the update details
121
+ get().updateSubscribers.forEach((callback) => callback(update));
122
+ },
123
+ formUpdateSubscribers: new Set(),
124
+ subscribeToFormUpdates: (callback) => {
125
+ const subscribers = get().formUpdateSubscribers;
126
+ subscribers.add(callback);
127
+ return () => {
128
+ get().formUpdateSubscribers.delete(callback);
129
+ };
130
+ },
131
+ notifyFormUpdate: (event) => {
132
+ get().formUpdateSubscribers.forEach((callback) => callback(event));
133
+ },
134
+ hookResults: new Map(),
135
+
136
+ setHookResult: (stateKey, pluginName, data) =>
137
+ set((state) => {
138
+ const next = new Map(state.hookResults);
139
+ const byPlugin = new Map(next.get(stateKey) ?? new Map());
140
+ if (data === undefined) byPlugin.delete(pluginName);
141
+ else byPlugin.set(pluginName, data);
142
+ if (byPlugin.size > 0) next.set(stateKey, byPlugin);
143
+ else next.delete(stateKey);
144
+ return { hookResults: next };
145
+ }),
146
+
147
+ getHookResult: (stateKey, pluginName) =>
148
+ get().hookResults.get(stateKey)?.get(pluginName),
149
+
150
+ removeHookResult: (stateKey, pluginName) =>
151
+ set((state) => {
152
+ const next = new Map(state.hookResults);
153
+ const byPlugin = new Map(next.get(stateKey) ?? new Map());
154
+ byPlugin.delete(pluginName);
155
+ if (byPlugin.size > 0) next.set(stateKey, byPlugin);
156
+ else next.delete(stateKey);
157
+ return { hookResults: next };
158
+ }),
159
+ }));