cogsbox-state 0.5.474 → 0.5.475-canary.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.
@@ -5,7 +5,11 @@ import {
5
5
  type FormOptsType,
6
6
  } from './CogsState';
7
7
  import { pluginStore } from './pluginStore';
8
- import { createMetadataContext, toDeconstructedMethods } from './plugins';
8
+ import {
9
+ createMetadataContext,
10
+ createScopedMetadataContext,
11
+ toDeconstructedMethods,
12
+ } from './plugins';
9
13
  import React, {
10
14
  memo,
11
15
  RefObject,
@@ -78,6 +82,34 @@ export function ValidationWrapper({
78
82
  : warningMessages.length > 0
79
83
  ? 'warning'
80
84
  : undefined;
85
+ const { registeredPlugins } = pluginStore.getState();
86
+ const pluginsApi: any = {};
87
+
88
+ // We iterate over ALL registered plugins in the app.
89
+ registeredPlugins.forEach((plugin) => {
90
+ // A plugin is considered "active" for this state key if its name
91
+ // exists as a key in the options (e.g., options.syncPlugin exists).
92
+ if (thisStateOpts && thisStateOpts.hasOwnProperty(plugin.name)) {
93
+ const pluginName = plugin.name;
94
+
95
+ // Now we can safely build the API for this active plugin.
96
+ const hookData = pluginStore
97
+ .getState()
98
+ .getHookResult(stateKey, pluginName);
99
+
100
+ const scopedMetadata = createScopedMetadataContext(
101
+ stateKey,
102
+ pluginName,
103
+ path
104
+ );
105
+
106
+ pluginsApi[pluginName] = {
107
+ hookData,
108
+ getFieldMetaData: scopedMetadata.getFieldMetaData,
109
+ setFieldMetaData: scopedMetadata.setFieldMetaData,
110
+ };
111
+ }
112
+ });
81
113
  return (
82
114
  <>
83
115
  {thisStateOpts?.formElements?.validation &&
@@ -96,6 +128,7 @@ export function ValidationWrapper({
96
128
  allErrors: errors,
97
129
  path: path,
98
130
  getData: () => getShadowValue(stateKey!, path),
131
+ plugins: pluginsApi,
99
132
  })
100
133
  ) : (
101
134
  <React.Fragment key={path.toString()}>{children}</React.Fragment>
@@ -244,17 +277,6 @@ export function FormElementWrapper({
244
277
  const isCurrentlyDebouncing = useRef(false);
245
278
  const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
246
279
 
247
- // 2. Memoize the list of active form wrappers to avoid re-calculating on every render.
248
- const activeFormWrappers = useMemo(() => {
249
- return (
250
- pluginStore
251
- .getState()
252
- .getPluginConfigsForState(stateKey)
253
- // We only care about plugins that have defined a formWrapper
254
- .filter((config) => typeof config.plugin.formWrapper === 'function')
255
- );
256
- }, [stateKey]);
257
-
258
280
  useEffect(() => {
259
281
  if (
260
282
  !isCurrentlyDebouncing.current &&
@@ -540,23 +562,9 @@ export function FormElementWrapper({
540
562
 
541
563
  const initialElement = renderFn(stateWithInputProps);
542
564
 
543
- const wrappedElement = activeFormWrappers.reduceRight(
544
- (currentElement, config, index) => (
545
- <PluginWrapper
546
- stateKey={stateKey}
547
- path={path}
548
- pluginName={config.plugin.name}
549
- wrapperDepth={activeFormWrappers.length - 1 - index}
550
- >
551
- {currentElement}
552
- </PluginWrapper>
553
- ),
554
- initialElement
555
- );
556
-
557
565
  return (
558
566
  <ValidationWrapper formOpts={formOpts} path={path} stateKey={stateKey}>
559
- {wrappedElement}
567
+ {initialElement}
560
568
  </ValidationWrapper>
561
569
  );
562
570
  }
@@ -568,18 +576,15 @@ export function useRegisterComponent(
568
576
  const fullComponentId = `${stateKey}////${componentId}`;
569
577
 
570
578
  useLayoutEffect(() => {
571
- // Call the safe, centralized function to register
572
579
  registerComponent(stateKey, fullComponentId, {
573
580
  forceUpdate: () => forceUpdate({}),
574
581
  paths: new Set(),
575
582
  reactiveType: ['component'],
576
583
  });
577
-
578
- // The cleanup now calls the safe, centralized unregister function
579
584
  return () => {
580
585
  unregisterComponent(stateKey, fullComponentId);
581
586
  };
582
- }, [stateKey, fullComponentId]); // Dependencies are stable and correct
587
+ }, [stateKey, fullComponentId]);
583
588
  }
584
589
 
585
590
  const useImageLoaded = (ref: RefObject<HTMLElement>): boolean => {
@@ -718,16 +723,20 @@ const PluginWrapper = memo(function PluginWrapper({
718
723
  return <>{children}</>;
719
724
  }
720
725
 
721
- const metadataContext = createMetadataContext(stateKey, plugin.name);
722
726
  const deconstructed = toDeconstructedMethods(stateHandler);
723
-
727
+ const scopedMetadataContext = createScopedMetadataContext(
728
+ stateKey,
729
+ plugin.name,
730
+ path
731
+ );
724
732
  return plugin.formWrapper({
725
733
  element: children,
726
734
  path,
727
735
  stateKey,
728
736
  pluginName: plugin.name,
729
737
  ...deconstructed,
730
- ...metadataContext,
738
+
739
+ ...scopedMetadataContext,
731
740
  options,
732
741
  hookData,
733
742
  fieldType: typeInfo?.type,
@@ -1,7 +1,11 @@
1
1
  import React, { useEffect, useMemo, useState, useRef, useReducer } from 'react';
2
2
  import { ClientActivityEvent, pluginStore } from './pluginStore';
3
3
  import { isDeepEqual } from './utility';
4
- import { createMetadataContext, toDeconstructedMethods } from './plugins';
4
+ import {
5
+ createMetadataContext,
6
+ createScopedMetadataContext,
7
+ toDeconstructedMethods,
8
+ } from './plugins';
5
9
  import type { CogsPlugin } from './plugins';
6
10
  import type { StateObject, UpdateTypeDetail } from './CogsState';
7
11
  import { ClientActivityState, FormEventType } from './store';
@@ -99,6 +103,13 @@ const PluginInstance = React.memo(
99
103
 
100
104
  const handleUpdate = (update: UpdateTypeDetail) => {
101
105
  if (update.stateKey === stateKey) {
106
+ // Create a new, SCOPED context for this specific path
107
+ const scopedMetadata = createScopedMetadataContext(
108
+ stateKey,
109
+ plugin.name,
110
+ update.path
111
+ );
112
+
102
113
  plugin.onUpdate!({
103
114
  stateKey,
104
115
  pluginName: plugin.name,
@@ -107,7 +118,7 @@ const PluginInstance = React.memo(
107
118
  options,
108
119
  hookData: hookDataRef.current,
109
120
  ...deconstructed,
110
- ...metadataContext,
121
+ ...scopedMetadata, // <-- Use the new scoped context
111
122
  });
112
123
  }
113
124
  };
@@ -116,7 +127,7 @@ const PluginInstance = React.memo(
116
127
  .getState()
117
128
  .subscribeToUpdates(handleUpdate);
118
129
  return unsubscribe;
119
- }, [stateKey, plugin, options, deconstructed, metadataContext]);
130
+ }, [stateKey, plugin, options, deconstructed]);
120
131
 
121
132
  useEffect(() => {
122
133
  if (!plugin.onFormUpdate) return;
@@ -125,15 +136,22 @@ const PluginInstance = React.memo(
125
136
  event: ClientActivityEvent // Use the proper type
126
137
  ) => {
127
138
  if (event.stateKey === stateKey) {
139
+ // Create a new, SCOPED context for this specific path
140
+ const scopedMetadata = createScopedMetadataContext(
141
+ stateKey,
142
+ plugin.name,
143
+ event.path
144
+ );
145
+
128
146
  plugin.onFormUpdate!({
129
147
  stateKey,
130
148
  pluginName: plugin.name,
131
149
  path: event.path,
132
- event: event, // Pass the whole event through, not a transformed version
150
+ event: event,
133
151
  options,
134
152
  hookData: hookDataRef.current,
135
153
  ...deconstructed,
136
- ...metadataContext,
154
+ ...scopedMetadata, // <-- Use the new scoped context
137
155
  });
138
156
  }
139
157
  };
@@ -142,7 +160,7 @@ const PluginInstance = React.memo(
142
160
  .getState()
143
161
  .subscribeToFormUpdates(handleFormUpdate);
144
162
  return unsubscribe;
145
- }, [stateKey, plugin, options, deconstructed, metadataContext]);
163
+ }, [stateKey, plugin, options, deconstructed]);
146
164
 
147
165
  return null;
148
166
  }
package/src/plugins.ts CHANGED
@@ -54,28 +54,36 @@ export function toDeconstructedMethods(stateHandler: StateObject<any>) {
54
54
  },
55
55
  };
56
56
  }
57
+ type ScopedMetadataMethods<TFieldMetaData> = {
58
+ getFieldMetaData: () => TFieldMetaData | undefined;
59
+ setFieldMetaData: (data: Partial<TFieldMetaData>) => void;
60
+ removeFieldMetaData: () => void;
61
+ };
57
62
 
63
+ // These are the existing global methods that still require a path.
64
+ type GlobalMetadataMethods<TFieldMetaData> = {
65
+ getFieldMetaData: (path: string[]) => TFieldMetaData | undefined;
66
+ setFieldMetaData: (path: string[], data: Partial<TFieldMetaData>) => void;
67
+ removeFieldMetaData: (path: string[]) => void;
68
+ };
58
69
  // Simplified: All params use the same TFieldMetaData type
59
70
  export type UseHookParams<
60
71
  TOptions,
61
72
  TPluginMetaData,
62
73
  TFieldMetaData,
63
74
  TStateSlice = any,
64
- > = DeconstructedCogsMethods<TStateSlice> & {
65
- stateKey: string;
75
+ > = DeconstructedCogsMethods<TStateSlice> &
76
+ GlobalMetadataMethods<TFieldMetaData> & {
77
+ stateKey: string;
66
78
 
67
- getPluginMetaData: () => TPluginMetaData | undefined;
68
- setPluginMetaData: (data: Partial<TPluginMetaData>) => void;
69
- removePluginMetaData: () => void;
70
-
71
- getFieldMetaData: (path: string[]) => TFieldMetaData | undefined;
72
- setFieldMetaData: (path: string[], data: Partial<TFieldMetaData>) => void;
73
- removeFieldMetaData: (path: string[]) => void;
79
+ getPluginMetaData: () => TPluginMetaData | undefined;
80
+ setPluginMetaData: (data: Partial<TPluginMetaData>) => void;
81
+ removePluginMetaData: () => void;
74
82
 
75
- options: TOptions;
76
- pluginName: string;
77
- isInitialMount: boolean;
78
- };
83
+ options: TOptions;
84
+ pluginName: string;
85
+ isInitialMount: boolean;
86
+ };
79
87
 
80
88
  export type TransformStateParams<
81
89
  TOptions,
@@ -83,23 +91,20 @@ export type TransformStateParams<
83
91
  TPluginMetaData,
84
92
  TFieldMetaData,
85
93
  TStateSlice = any,
86
- > = DeconstructedCogsMethods<TStateSlice> & {
87
- stateKey: string;
88
-
89
- getPluginMetaData: () => TPluginMetaData | undefined;
90
- setPluginMetaData: (data: Partial<TPluginMetaData>) => void;
91
- removePluginMetaData: () => void;
92
-
93
- getFieldMetaData: (path: string[]) => TFieldMetaData | undefined;
94
- setFieldMetaData: (path: string[], data: Partial<TFieldMetaData>) => void;
95
- removeFieldMetaData: (path: string[]) => void;
96
-
97
- options: TOptions;
98
- hookData?: THookReturn;
99
- previousState?: TStateSlice;
100
- isInitialTransform: boolean;
101
- pluginName: string;
102
- };
94
+ > = DeconstructedCogsMethods<TStateSlice> &
95
+ GlobalMetadataMethods<TFieldMetaData> & {
96
+ stateKey: string;
97
+
98
+ getPluginMetaData: () => TPluginMetaData | undefined;
99
+ setPluginMetaData: (data: Partial<TPluginMetaData>) => void;
100
+ removePluginMetaData: () => void;
101
+
102
+ options: TOptions;
103
+ hookData?: THookReturn;
104
+ previousState?: TStateSlice;
105
+ isInitialTransform: boolean;
106
+ pluginName: string;
107
+ };
103
108
 
104
109
  export type OnUpdateParams<
105
110
  TOptions,
@@ -107,55 +112,49 @@ export type OnUpdateParams<
107
112
  TPluginMetaData,
108
113
  TFieldMetaData,
109
114
  TStateSlice = any,
110
- > = DeconstructedCogsMethods<TStateSlice> & {
111
- stateKey: string;
112
-
113
- getPluginMetaData: () => TPluginMetaData | undefined;
114
- setPluginMetaData: (data: Partial<TPluginMetaData>) => void;
115
- removePluginMetaData: () => void;
115
+ > = DeconstructedCogsMethods<TStateSlice> &
116
+ ScopedMetadataMethods<TFieldMetaData> & {
117
+ stateKey: string;
116
118
 
117
- getFieldMetaData: (path: string[]) => TFieldMetaData | undefined;
118
- setFieldMetaData: (path: string[], data: Partial<TFieldMetaData>) => void;
119
- removeFieldMetaData: (path: string[]) => void;
119
+ getPluginMetaData: () => TPluginMetaData | undefined;
120
+ setPluginMetaData: (data: Partial<TPluginMetaData>) => void;
121
+ removePluginMetaData: () => void;
120
122
 
121
- update: UpdateTypeDetail;
122
- path?: string[];
123
+ update: UpdateTypeDetail;
124
+ path?: string[];
123
125
 
124
- options: TOptions;
125
- hookData?: THookReturn;
126
+ options: TOptions;
127
+ hookData?: THookReturn;
126
128
 
127
- previousValue?: any;
128
- nextValue?: any;
129
- updateSource?: 'user' | 'plugin' | 'system';
129
+ previousValue?: any;
130
+ nextValue?: any;
131
+ updateSource?: 'user' | 'plugin' | 'system';
130
132
 
131
- pluginName: string;
132
- };
133
+ pluginName: string;
134
+ };
133
135
  export type OnFormUpdateParams<
134
136
  TOptions,
135
137
  THookReturn,
136
138
  TPluginMetaData,
137
139
  TFieldMetaData,
138
140
  TStateSlice = any,
139
- > = DeconstructedCogsMethods<TStateSlice> & {
140
- stateKey: string;
141
-
142
- getPluginMetaData: () => TPluginMetaData | undefined;
143
- setPluginMetaData: (data: Partial<TPluginMetaData>) => void;
144
- removePluginMetaData: () => void;
141
+ > = DeconstructedCogsMethods<TStateSlice> &
142
+ ScopedMetadataMethods<TFieldMetaData> & {
143
+ stateKey: string;
145
144
 
146
- getFieldMetaData: (path: string[]) => TFieldMetaData | undefined;
147
- setFieldMetaData: (path: string[], data: Partial<TFieldMetaData>) => void;
148
- removeFieldMetaData: (path: string[]) => void;
145
+ getPluginMetaData: () => TPluginMetaData | undefined;
146
+ setPluginMetaData: (data: Partial<TPluginMetaData>) => void;
147
+ removePluginMetaData: () => void;
149
148
 
150
- path: string[];
151
- event: ClientActivityEvent; // Update this to use the full event type
149
+ path: string[];
150
+ event: ClientActivityEvent; // Update this to use the full event type
152
151
 
153
- options: TOptions;
154
- hookData?: THookReturn;
152
+ options: TOptions;
153
+ hookData?: THookReturn;
155
154
 
156
- formState?: 'pristine' | 'dirty' | 'submitting' | 'submitted';
157
- pluginName: string;
158
- };
155
+ formState?: 'pristine' | 'dirty' | 'submitting' | 'submitted';
156
+ pluginName: string;
157
+ };
159
158
 
160
159
  export type FormWrapperParams<
161
160
  TOptions,
@@ -163,7 +162,7 @@ export type FormWrapperParams<
163
162
  TPluginMetaData,
164
163
  TFieldMetaData,
165
164
  TStateSlice = any,
166
- > = {
165
+ > = ScopedMetadataMethods<TFieldMetaData> & {
167
166
  element: React.ReactNode;
168
167
  path: string[];
169
168
  stateKey: string;
@@ -184,10 +183,6 @@ export type FormWrapperParams<
184
183
  setPluginMetaData: (data: Partial<TPluginMetaData>) => void;
185
184
  removePluginMetaData: () => void;
186
185
 
187
- getFieldMetaData: (path: string[]) => TFieldMetaData | undefined;
188
- setFieldMetaData: (path: string[], data: Partial<TFieldMetaData>) => void;
189
- removeFieldMetaData: (path: string[]) => void;
190
-
191
186
  pluginName: string;
192
187
  };
193
188
 
@@ -246,21 +241,6 @@ export type CogsPlugin<
246
241
  ) => React.ReactNode;
247
242
  };
248
243
 
249
- // Extract plugin options helper
250
- export type ExtractPluginOptions<
251
- TPlugins extends readonly CogsPlugin<any, any, any, any, any>[],
252
- > = {
253
- [P in TPlugins[number] as P['name']]?: P extends CogsPlugin<
254
- any,
255
- infer O,
256
- any,
257
- any,
258
- any
259
- >
260
- ? O
261
- : never;
262
- };
263
-
264
244
  // Metadata helpers
265
245
  export function createMetadataContext<TPluginMetaData, TFieldMetaData>(
266
246
  stateKey: string,
@@ -299,6 +279,29 @@ export function createMetadataContext<TPluginMetaData, TFieldMetaData>(
299
279
  };
300
280
  }
301
281
 
282
+ export function createScopedMetadataContext<TPluginMetaData, TFieldMetaData>(
283
+ stateKey: string,
284
+ pluginName: string,
285
+ path: string[]
286
+ ) {
287
+ const globalContext = createMetadataContext<TPluginMetaData, TFieldMetaData>(
288
+ stateKey,
289
+ pluginName
290
+ );
291
+
292
+ return {
293
+ // Return the global methods for plugin metadata
294
+ ...globalContext,
295
+ // Override the field methods with new, path-scoped versions
296
+ getFieldMetaData: (): TFieldMetaData | undefined =>
297
+ globalContext.getFieldMetaData(path),
298
+
299
+ setFieldMetaData: (data: Partial<TFieldMetaData>) =>
300
+ globalContext.setFieldMetaData(path, data),
301
+
302
+ removeFieldMetaData: () => globalContext.removeFieldMetaData(path),
303
+ };
304
+ }
302
305
  type ZodObjOutput<T extends z.ZodObject<any>> = {
303
306
  [K in keyof T['shape']]: z.output<T['shape'][K]>;
304
307
  };
@@ -343,15 +346,6 @@ export function createPluginContext<
343
346
  >
344
347
  ) => void;
345
348
 
346
- type FormWrapperFn<THookReturn> = (
347
- params: FormWrapperParams<
348
- Options,
349
- THookReturn,
350
- PluginMetaData,
351
- FieldMetaData
352
- >
353
- ) => React.ReactNode;
354
-
355
349
  type Plugin<THookReturn> = Prettify<
356
350
  CogsPlugin<TName, Options, THookReturn, PluginMetaData, FieldMetaData>
357
351
  >;
@@ -362,8 +356,7 @@ export function createPluginContext<
362
356
  ) => THookReturn,
363
357
  transformFn?: TransformFn<THookReturn>,
364
358
  updateHandler?: UpdateFn<THookReturn>,
365
- formUpdateHandler?: FormUpdateFn<THookReturn>,
366
- formWrapper?: FormWrapperFn<THookReturn>
359
+ formUpdateHandler?: FormUpdateFn<THookReturn>
367
360
  ): Plugin<THookReturn> => {
368
361
  return {
369
362
  name,
@@ -371,7 +364,6 @@ export function createPluginContext<
371
364
  transformState: transformFn as any,
372
365
  onUpdate: updateHandler as any,
373
366
  onFormUpdate: formUpdateHandler as any,
374
- formWrapper: formWrapper as any,
375
367
  };
376
368
  };
377
369
 
@@ -414,19 +406,6 @@ export function createPluginContext<
414
406
  onFormUpdate(
415
407
  fn: FormUpdateFn<THookReturn>
416
408
  ): BuildRet<THookReturn, HasTransform, HasUpdate, true, HasWrapper>;
417
- }) &
418
- (HasWrapper extends true
419
- ? {}
420
- : {
421
- formWrapper(
422
- fn: FormWrapperFn<THookReturn>
423
- ): BuildRet<
424
- THookReturn,
425
- HasTransform,
426
- HasUpdate,
427
- HasFormUpdate,
428
- true
429
- >;
430
409
  });
431
410
 
432
411
  function createBuilder<
@@ -441,8 +420,7 @@ export function createPluginContext<
441
420
  ) => THookReturn,
442
421
  transformFn?: TransformFn<THookReturn>,
443
422
  updateHandler?: UpdateFn<THookReturn>,
444
- formUpdateHandler?: FormUpdateFn<THookReturn>,
445
- formWrapper?: FormWrapperFn<THookReturn>
423
+ formUpdateHandler?: FormUpdateFn<THookReturn>
446
424
  ): BuildRet<
447
425
  THookReturn,
448
426
  HasTransform,
@@ -454,8 +432,7 @@ export function createPluginContext<
454
432
  hookFn,
455
433
  transformFn,
456
434
  updateHandler,
457
- formUpdateHandler,
458
- formWrapper
435
+ formUpdateHandler
459
436
  );
460
437
 
461
438
  const methods = {} as Partial<
@@ -476,7 +453,7 @@ export function createPluginContext<
476
453
  HasUpdate,
477
454
  HasFormUpdate,
478
455
  HasWrapper
479
- >(hookFn, fn, updateHandler, formUpdateHandler, formWrapper);
456
+ >(hookFn, fn, updateHandler, formUpdateHandler);
480
457
  }
481
458
  if (!updateHandler) {
482
459
  (methods as any).onUpdate = (fn: UpdateFn<THookReturn>) =>
@@ -486,7 +463,7 @@ export function createPluginContext<
486
463
  true,
487
464
  HasFormUpdate,
488
465
  HasWrapper
489
- >(hookFn, transformFn, fn, formUpdateHandler, formWrapper);
466
+ >(hookFn, transformFn, fn, formUpdateHandler);
490
467
  }
491
468
  if (!formUpdateHandler) {
492
469
  (methods as any).onFormUpdate = (fn: FormUpdateFn<THookReturn>) =>
@@ -494,20 +471,9 @@ export function createPluginContext<
494
471
  hookFn,
495
472
  transformFn,
496
473
  updateHandler,
497
- fn,
498
- formWrapper
474
+ fn
499
475
  );
500
476
  }
501
- if (!formWrapper) {
502
- (methods as any).formWrapper = (fn: FormWrapperFn<THookReturn>) =>
503
- createBuilder<
504
- THookReturn,
505
- HasTransform,
506
- HasUpdate,
507
- HasFormUpdate,
508
- true
509
- >(hookFn, transformFn, updateHandler, formUpdateHandler, fn);
510
- }
511
477
 
512
478
  return Object.assign(plugin, methods) as BuildRet<
513
479
  THookReturn,
package/src/store.ts CHANGED
@@ -1232,7 +1232,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
1232
1232
  `Array not found at path: ${[key, ...arrayPath].join('.')}`
1233
1233
  );
1234
1234
  }
1235
- console.log('OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO');
1235
+
1236
1236
  const newItemId = itemId || `${generateId(key)}`;
1237
1237
 
1238
1238
  // BUILD AND ADD the node directly - no need for addItemsToArrayNode