cogsbox-state 0.5.481 → 0.5.483

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/src/CogsState.tsx CHANGED
@@ -15,6 +15,10 @@ import {
15
15
  useRef,
16
16
  useState,
17
17
  type CSSProperties,
18
+ type ChangeEvent,
19
+ type FocusEvent,
20
+ type JSX,
21
+ type MutableRefObject,
18
22
  type ReactNode,
19
23
  type RefObject,
20
24
  } from 'react';
@@ -61,15 +65,15 @@ export type SyncInfo = {
61
65
 
62
66
  export type FormElementParams<T> = StateObject<T> & {
63
67
  $inputProps: {
64
- ref?: React.RefObject<any>;
68
+ ref?: RefObject<any>;
65
69
  value?: T extends boolean ? never : T;
66
70
  onChange?: (
67
- event: React.ChangeEvent<
71
+ event: ChangeEvent<
68
72
  HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
69
73
  >
70
74
  ) => void;
71
75
  onBlur?: (
72
- event: React.FocusEvent<
76
+ event: FocusEvent<
73
77
  HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
74
78
  >
75
79
  ) => void;
@@ -165,11 +169,11 @@ export type EndType<
165
169
  $_stateKey: string;
166
170
  $isolate: {
167
171
  (
168
- renderFn: (state: StateObject<T, TPlugins>) => React.ReactNode
172
+ renderFn: (state: StateObject<T, TPlugins>) => ReactNode
169
173
  ): JSX.Element;
170
174
  (
171
175
  dependencies: any[],
172
- renderFn: (state: StateObject<T, TPlugins>) => React.ReactNode
176
+ renderFn: (state: StateObject<T, TPlugins>) => ReactNode
173
177
  ): JSX.Element;
174
178
  };
175
179
  $formElement: (
@@ -202,7 +206,7 @@ export type EndType<
202
206
  children,
203
207
  hideMessage,
204
208
  }: {
205
- children: React.ReactNode;
209
+ children: ReactNode;
206
210
  hideMessage?: boolean;
207
211
  }) => JSX.Element;
208
212
  $lastSynced?: SyncInfo;
@@ -542,7 +546,7 @@ export type FormsElementsType<
542
546
  > = {
543
547
  // These optional, built-in wrappers are unchanged.
544
548
  validation?: (options: {
545
- children: React.ReactNode;
549
+ children: ReactNode;
546
550
  status: ValidationStatus;
547
551
  severity: ValidationSeverity;
548
552
  hasErrors: boolean;
@@ -564,13 +568,13 @@ export type FormsElementsType<
564
568
  ScopedPluginApi<THookReturn, TFieldMetaData>
565
569
  : never;
566
570
  };
567
- }) => React.ReactNode;
571
+ }) => ReactNode;
568
572
  syncRender?: (options: {
569
- children: React.ReactNode;
573
+ children: ReactNode;
570
574
  time: number;
571
575
  data?: TState;
572
576
  key?: string;
573
- }) => React.ReactNode;
577
+ }) => ReactNode;
574
578
  };
575
579
  const {
576
580
  getInitialOptions,
@@ -824,31 +828,42 @@ type KeyedKeys<P> = {
824
828
  : never;
825
829
  }[keyof P];
826
830
 
831
+ type PluginOptionEntry<
832
+ P,
833
+ StateKey extends PropertyKey,
834
+ > = P extends undefined
835
+ ? never
836
+ : P extends Record<string, any>
837
+ ? Prettify<
838
+ Partial<Pick<P, Exclude<keyof P, KeyedKeys<P>>>> & {
839
+ [K in KeyedKeys<P> as StateKey extends keyof NonNullable<
840
+ P[K]
841
+ >['map']
842
+ ? NonNullable<P[K]>['map'][StateKey] extends undefined
843
+ ? never
844
+ : keyof NonNullable<P[K]>['map'][StateKey] extends never
845
+ ? never
846
+ : K
847
+ : never]: CleanIntersection<
848
+ StateKey extends keyof NonNullable<P[K]>['map']
849
+ ? NonNullable<P[K]>['map'][StateKey]
850
+ : never
851
+ >;
852
+ }
853
+ >
854
+ : P extends object
855
+ ? Partial<P> extends P
856
+ ? Partial<P>
857
+ : P
858
+ : P;
859
+
827
860
  type PluginOptionsForState<
828
861
  PluginOptions,
829
862
  StateKey extends PropertyKey,
830
863
  > = {
831
- [PName in keyof PluginOptions]?: PluginOptions[PName] extends infer P
832
- ? P extends Record<string, any>
833
- ? Prettify<
834
- Partial<Pick<P, Exclude<keyof P, KeyedKeys<P>>>> & {
835
- [K in KeyedKeys<P> as StateKey extends keyof NonNullable<
836
- P[K]
837
- >['map']
838
- ? NonNullable<P[K]>['map'][StateKey] extends undefined
839
- ? never
840
- : keyof NonNullable<P[K]>['map'][StateKey] extends never
841
- ? never
842
- : K
843
- : never]: CleanIntersection<
844
- StateKey extends keyof NonNullable<P[K]>['map']
845
- ? NonNullable<P[K]>['map'][StateKey]
846
- : never
847
- >;
848
- }
849
- >
850
- : P
851
- : never;
864
+ [PName in keyof PluginOptions as PluginOptions[PName] extends undefined
865
+ ? never
866
+ : PName]?: PluginOptionEntry<PluginOptions[PName], StateKey>;
852
867
  };
853
868
 
854
869
  type UseCogsStateOptions<
@@ -983,29 +998,8 @@ export const createCogsState = <
983
998
  const useCogsState = <StateKey extends StateKeys>(
984
999
  stateKey: StateKey,
985
1000
  options?: Prettify<
986
- OptionsType<StateSlice<StateKey>, never> & {
987
- [PName in keyof PluginOptions]?: PluginOptions[PName] extends infer P
988
- ? P extends Record<string, any>
989
- ? Prettify<
990
- Partial<Pick<P, Exclude<keyof P, KeyedKeys<P>>>> & {
991
- [K in KeyedKeys<P> as StateKey extends keyof NonNullable<
992
- P[K]
993
- >['map']
994
- ? NonNullable<P[K]>['map'][StateKey] extends undefined
995
- ? never
996
- : keyof NonNullable<P[K]>['map'][StateKey] extends never
997
- ? never
998
- : K
999
- : never]: CleanIntersection<
1000
- StateKey extends keyof NonNullable<P[K]>['map']
1001
- ? NonNullable<P[K]>['map'][StateKey]
1002
- : never
1003
- >;
1004
- }
1005
- >
1006
- : P
1007
- : never;
1008
- }
1001
+ OptionsType<StateSlice<StateKey>, never> &
1002
+ PluginOptionsForState<PluginOptions, StateKey>
1009
1003
  >
1010
1004
  ): StateObject<StateSlice<StateKey>> => {
1011
1005
  const [componentId] = useState(options?.componentId ?? uuidv4());
@@ -1046,10 +1040,10 @@ export const createCogsState = <
1046
1040
  useEffect(() => {
1047
1041
  pluginStore
1048
1042
  .getState()
1049
- .stateHandlers.set(stateKey as string, updater as any);
1043
+ .registerStateHandler(stateKey as string, updater as any);
1050
1044
 
1051
1045
  return () => {
1052
- pluginStore.getState().stateHandlers.delete(stateKey as string);
1046
+ pluginStore.getState().unregisterStateHandler(stateKey as string);
1053
1047
  };
1054
1048
  }, [stateKey, updater]);
1055
1049
 
@@ -1630,7 +1624,7 @@ function flushQueue() {
1630
1624
  function createEffectiveSetState<T>(
1631
1625
  thisKey: string,
1632
1626
  sessionId: string | undefined,
1633
- latestInitialOptionsRef: React.MutableRefObject<OptionsType<T> | null>
1627
+ latestInitialOptionsRef: MutableRefObject<OptionsType<T> | null>
1634
1628
  ): EffectiveSetState<T> {
1635
1629
  return (newStateOrFunction, path, updateObj) => {
1636
1630
  executeUpdate(thisKey, path, newStateOrFunction, updateObj);
@@ -1733,7 +1727,7 @@ export function useCogsStateFn<
1733
1727
  defaultState?: TStateObject;
1734
1728
  syncOptions?: SyncOptionsType<any>;
1735
1729
  } & OptionsType<TStateObject> = {}
1736
- ) {
1730
+ ): StateObject<TStateObject, TPlugins> {
1737
1731
  const [reactiveForce, forceUpdate] = useState({}); //this is the key to reactivity
1738
1732
  const { sessionId } = useCogsConfig();
1739
1733
  let noStateKey = stateKey ? false : true;
@@ -3556,7 +3550,7 @@ function createProxyHandler<
3556
3550
  children,
3557
3551
  hideMessage,
3558
3552
  }: {
3559
- children: React.ReactNode;
3553
+ children: ReactNode;
3560
3554
  hideMessage?: boolean;
3561
3555
  }) => (
3562
3556
  <ValidationWrapper
@@ -3618,8 +3612,8 @@ function createProxyHandler<
3618
3612
  if (prop === '$isolate') {
3619
3613
  // We accept (renderFn) OR (deps, renderFn)
3620
3614
  return (
3621
- arg1: any[] | ((state: any) => React.ReactNode),
3622
- arg2?: (state: any) => React.ReactNode
3615
+ arg1: any[] | ((state: any) => ReactNode),
3616
+ arg2?: (state: any) => ReactNode
3623
3617
  ) => {
3624
3618
  // Check if the first argument is the dependency array
3625
3619
  const hasDependencies = Array.isArray(arg1);
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import React, { createContext, useContext } from "react";
3
+ import React, { createContext, useContext, type ReactNode } from "react";
4
4
  type ConfigType = { sessionId?: string };
5
5
  export const config: ConfigType = {
6
6
  sessionId: undefined,
@@ -14,7 +14,7 @@ export function CogsStateClient({
14
14
  children,
15
15
  sessionId,
16
16
  }: {
17
- children: React.ReactNode;
17
+ children: ReactNode;
18
18
  sessionId?: string;
19
19
  }) {
20
20
  return (
@@ -20,6 +20,7 @@ import React, {
20
20
  useRef,
21
21
  useState,
22
22
  useMemo,
23
+ type ReactNode,
23
24
  } from 'react';
24
25
  import { getGlobalStore, ValidationError, ValidationSeverity } from './store';
25
26
  import { useInView } from 'react-intersection-observer';
@@ -46,7 +47,7 @@ export type ValidationWrapperProps = {
46
47
  formOpts?: FormOptsType;
47
48
  path: string[];
48
49
  stateKey: string;
49
- children: React.ReactNode;
50
+ children: ReactNode;
50
51
  };
51
52
 
52
53
  export function ValidationWrapper({
@@ -241,7 +242,7 @@ export function FormElementWrapper({
241
242
  componentId: string;
242
243
  meta?: any;
243
244
  }) => any;
244
- renderFn: (params: FormElementParams<any>) => React.ReactNode;
245
+ renderFn: (params: FormElementParams<any>) => ReactNode;
245
246
  formOpts?: FormOptsType;
246
247
  setState: any;
247
248
  }) {
@@ -626,7 +627,7 @@ export function IsolatedComponentWrapper({
626
627
  }
627
628
  // 1. Define the MINIMAL props needed.
628
629
  type PluginWrapperProps = {
629
- children: React.ReactNode;
630
+ children: ReactNode;
630
631
  stateKey: string;
631
632
  path: string[];
632
633
  pluginName: string;
@@ -1,4 +1,11 @@
1
- import React, { useEffect, useMemo, useState, useRef, useReducer } from 'react';
1
+ import React, {
2
+ useEffect,
3
+ useMemo,
4
+ useState,
5
+ useRef,
6
+ useSyncExternalStore,
7
+ type ReactNode,
8
+ } from 'react';
2
9
  import { ClientActivityEvent, pluginStore } from './pluginStore';
3
10
  import { isDeepEqual } from './utility';
4
11
  import {
@@ -168,52 +175,42 @@ const PluginInstance = React.memo(
168
175
  * The main orchestrator component. It reads from the central pluginStore
169
176
  * and renders a `PluginInstance` controller for each active plugin.
170
177
  */
171
- export function PluginRunner({ children }: { children: React.ReactNode }) {
172
- // A simple way to force a re-render when the store changes.
173
- const [, forceUpdate] = useReducer((c) => c + 1, 0);
174
-
175
- // Subscribe to the store. When plugins or their options are added/removed,
176
- // this component will re-render to update the list of PluginInstances.
177
- useEffect(() => {
178
- const unsubscribe = pluginStore.subscribe(forceUpdate);
179
-
180
- return unsubscribe;
181
- }, []);
182
-
178
+ export function PluginRunner({ children }: { children: ReactNode }) {
183
179
  const { pluginOptions, stateHandlers, registeredPlugins } =
184
- pluginStore.getState();
180
+ useSyncExternalStore(
181
+ pluginStore.subscribe,
182
+ () => pluginStore.getState(),
183
+ () => pluginStore.getState()
184
+ );
185
+
186
+ const pluginNeedsRunner = (
187
+ plugin: CogsPlugin<any, any, any, any, any, any, any>
188
+ ) =>
189
+ !!(
190
+ plugin.useHook ||
191
+ plugin.transformState ||
192
+ plugin.onUpdate ||
193
+ plugin.onFormUpdate
194
+ );
185
195
 
186
196
  return (
187
197
  <>
188
- {/*
189
- This declarative mapping is the core of the solution.
190
- React will now manage adding and removing `PluginInstance` components
191
- as the application state changes, ensuring hooks are handled safely.
192
- */}
193
- {Array.from(pluginOptions.entries()).map(([stateKey, pluginMap]) => {
194
- const stateHandler = stateHandlers.get(stateKey);
195
- if (!stateHandler) {
196
- return null; // Don't render a runner if the state handler isn't ready.
197
- }
198
-
199
- return Array.from(pluginMap.entries()).map(([pluginName, options]) => {
200
- const plugin = registeredPlugins.find((p) => p.name === pluginName);
201
- if (!plugin) {
202
- return null; // Don't render if the plugin is not in the registered list.
203
- }
198
+ {Array.from(stateHandlers.entries()).flatMap(([stateKey, stateHandler]) =>
199
+ registeredPlugins.filter(pluginNeedsRunner).map((plugin) => {
200
+ const options =
201
+ pluginOptions.get(stateKey)?.get(plugin.name) ?? {};
204
202
 
205
- // Render a dedicated, memoized controller for this specific plugin configuration.
206
203
  return (
207
204
  <PluginInstance
208
- key={`${stateKey}:${pluginName}`}
205
+ key={`${stateKey}:${plugin.name}`}
209
206
  stateKey={stateKey}
210
207
  plugin={plugin}
211
208
  options={options}
212
209
  stateHandler={stateHandler}
213
210
  />
214
211
  );
215
- });
216
- })}
212
+ })
213
+ )}
217
214
 
218
215
  {children}
219
216
  </>
@@ -39,6 +39,7 @@ export type ClientActivityEvent = {
39
39
  type PluginRegistryStore = {
40
40
  stateHandlers: Map<string, StateObject<any>>; // stateKey -> handler
41
41
  registerStateHandler: (stateKey: string, handler: StateObject<any>) => void;
42
+ unregisterStateHandler: (stateKey: string) => void;
42
43
  registeredPlugins: readonly CogsPlugin<any, any, any, any, any>[];
43
44
  setRegisteredPlugins: (
44
45
  plugins: readonly CogsPlugin<any, any, any, any, any>[]
@@ -78,7 +79,12 @@ export const pluginStore = create<PluginRegistryStore>((set, get) => ({
78
79
  set((state) => {
79
80
  const newMap = new Map(state.stateHandlers);
80
81
  newMap.set(stateKey, handler);
81
- console.log('addign handler', stateKey, handler);
82
+ return { stateHandlers: newMap };
83
+ }),
84
+ unregisterStateHandler: (stateKey) =>
85
+ set((state) => {
86
+ const newMap = new Map(state.stateHandlers);
87
+ newMap.delete(stateKey);
82
88
  return { stateHandlers: newMap };
83
89
  }),
84
90
  registeredPlugins: [],
package/src/plugins.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import type React from 'react';
2
+ import type { ReactNode, RefObject } from 'react';
3
3
  import { StateObject, UpdateTypeDetail } from './CogsState';
4
4
  import {
5
5
  getGlobalStore,
@@ -7,7 +7,6 @@ import {
7
7
  setAllFieldsDisabled,
8
8
  } from './store';
9
9
  import { ClientActivityEvent } from './pluginStore';
10
- import { RefObject } from 'react';
11
10
 
12
11
  type Prettify<T> = { [K in keyof T]: T[K] } & {};
13
12
 
@@ -228,7 +227,7 @@ export type FormWrapperParams<
228
227
  TFieldMetaData,
229
228
  TStateSlice = any,
230
229
  > = ScopedMetadataMethods<TFieldMetaData> & {
231
- element: React.ReactNode;
230
+ element: ReactNode;
232
231
  path: string[];
233
232
  stateKey: string;
234
233
  options: TOptions;
@@ -307,7 +306,7 @@ export type CogsPlugin<
307
306
  TFieldMetaData,
308
307
  any
309
308
  >
310
- ) => React.ReactNode;
309
+ ) => ReactNode;
311
310
 
312
311
  chainMethods?: TChainMethods;
313
312
  };
@@ -602,7 +601,7 @@ export type PluginFormWrapperFn<
602
601
  TFieldMetaData,
603
602
  any
604
603
  >
605
- ) => React.ReactNode;
604
+ ) => ReactNode;
606
605
 
607
606
  export type CogsPluginBuilder<
608
607
  TName extends string,
@@ -774,7 +773,7 @@ export type CogsPluginBuilder<
774
773
  true,
775
774
  TNewState
776
775
  >;
777
- });
776
+ });
778
777
 
779
778
  export type CreatePluginStart<
780
779
  TName extends string,
package/src/store.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { create } from 'zustand';
2
+ import type { RefObject } from 'react';
2
3
 
3
4
  import type {
4
5
  OptionsType,
@@ -79,7 +80,7 @@ export type ClientActivityState = {
79
80
  string,
80
81
  {
81
82
  // componentId -> element info
82
- domRef: React.RefObject<HTMLElement>;
83
+ domRef: RefObject<HTMLElement>;
83
84
  elementType:
84
85
  | 'input'
85
86
  | 'textarea'