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.
- package/dist/CogsState.d.ts +98 -82
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1020 -950
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.jsx +276 -217
- package/dist/Components.jsx.map +1 -1
- package/dist/PluginRunner.d.ts +10 -0
- package/dist/PluginRunner.d.ts.map +1 -0
- package/dist/PluginRunner.jsx +128 -0
- package/dist/PluginRunner.jsx.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -26
- package/dist/index.js.map +1 -1
- package/dist/pluginStore.d.ts +43 -0
- package/dist/pluginStore.d.ts.map +1 -0
- package/dist/pluginStore.js +52 -0
- package/dist/pluginStore.js.map +1 -0
- package/dist/plugins.d.ts +1326 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +76 -0
- package/dist/plugins.js.map +1 -0
- package/dist/store.d.ts +50 -15
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +509 -470
- package/dist/store.js.map +1 -1
- package/dist/utility.d.ts +1 -1
- package/dist/utility.d.ts.map +1 -1
- package/dist/utility.js +12 -12
- package/dist/utility.js.map +1 -1
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +39 -0
- package/dist/validation.js.map +1 -0
- package/package.json +13 -3
- package/src/CogsState.tsx +658 -457
- package/src/Components.tsx +272 -194
- package/src/PluginRunner.tsx +208 -0
- package/src/index.ts +2 -0
- package/src/pluginStore.ts +159 -0
- package/src/plugins.ts +548 -0
- package/src/store.ts +748 -493
- package/src/utility.ts +31 -31
- package/src/validation.ts +84 -0
package/src/CogsState.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
2
|
+
import { pluginStore } from './pluginStore';
|
|
3
|
+
import { FormWrapperParams, type CogsPlugin } from './plugins';
|
|
3
4
|
import {
|
|
4
5
|
createElement,
|
|
5
6
|
startTransition,
|
|
@@ -15,10 +16,11 @@ import {
|
|
|
15
16
|
} from 'react';
|
|
16
17
|
|
|
17
18
|
import {
|
|
18
|
-
|
|
19
|
-
isArray,
|
|
19
|
+
transformStateFunc,
|
|
20
20
|
isFunction,
|
|
21
21
|
type GenericObject,
|
|
22
|
+
isArray,
|
|
23
|
+
getDifferences,
|
|
22
24
|
} from './utility.js';
|
|
23
25
|
import {
|
|
24
26
|
FormElementWrapper,
|
|
@@ -26,13 +28,13 @@ import {
|
|
|
26
28
|
MemoizedCogsItemWrapper,
|
|
27
29
|
ValidationWrapper,
|
|
28
30
|
} from './Components.js';
|
|
29
|
-
import { isDeepEqual
|
|
31
|
+
import { isDeepEqual } from './utility.js';
|
|
30
32
|
import superjson from 'superjson';
|
|
31
33
|
import { v4 as uuidv4 } from 'uuid';
|
|
32
34
|
|
|
33
35
|
import {
|
|
34
|
-
formRefStore,
|
|
35
36
|
getGlobalStore,
|
|
37
|
+
updateShadowTypeInfo,
|
|
36
38
|
ValidationError,
|
|
37
39
|
ValidationSeverity,
|
|
38
40
|
ValidationStatus,
|
|
@@ -44,7 +46,9 @@ import { Operation } from 'fast-json-patch';
|
|
|
44
46
|
import * as z3 from 'zod/v3';
|
|
45
47
|
import * as z4 from 'zod/v4';
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
import { runValidation } from './validation';
|
|
50
|
+
|
|
51
|
+
export type Prettify<T> = T extends any ? { [K in keyof T]: T[K] } : never;
|
|
48
52
|
|
|
49
53
|
export type VirtualViewOptions = {
|
|
50
54
|
itemHeight?: number;
|
|
@@ -101,37 +105,7 @@ type CutFunctionType<T> = (
|
|
|
101
105
|
) => StateObject<T>;
|
|
102
106
|
|
|
103
107
|
export type InferArrayElement<T> = T extends (infer U)[] ? U : never;
|
|
104
|
-
|
|
105
|
-
| 'concat'
|
|
106
|
-
| 'copyWithin'
|
|
107
|
-
| 'fill'
|
|
108
|
-
| 'find'
|
|
109
|
-
| 'findIndex'
|
|
110
|
-
| 'flat'
|
|
111
|
-
| 'flatMap'
|
|
112
|
-
| 'includes'
|
|
113
|
-
| 'indexOf'
|
|
114
|
-
| 'join'
|
|
115
|
-
| 'keys'
|
|
116
|
-
| 'lastIndexOf'
|
|
117
|
-
| 'map'
|
|
118
|
-
| 'pop'
|
|
119
|
-
| 'push'
|
|
120
|
-
| 'reduce'
|
|
121
|
-
| 'reduceRight'
|
|
122
|
-
| 'reverse'
|
|
123
|
-
| 'shift'
|
|
124
|
-
| 'slice'
|
|
125
|
-
| 'some'
|
|
126
|
-
| 'sort'
|
|
127
|
-
| 'splice'
|
|
128
|
-
| 'unshift'
|
|
129
|
-
| 'values'
|
|
130
|
-
| 'entries'
|
|
131
|
-
| 'every'
|
|
132
|
-
| 'filter'
|
|
133
|
-
| 'forEach'
|
|
134
|
-
| 'with';
|
|
108
|
+
|
|
135
109
|
export type StreamOptions<T, R = T> = {
|
|
136
110
|
bufferSize?: number;
|
|
137
111
|
flushInterval?: number;
|
|
@@ -162,6 +136,7 @@ export type ArrayEndType<TShape extends unknown> = {
|
|
|
162
136
|
$_index: number;
|
|
163
137
|
} & EndType<Prettify<InferArrayElement<TShape>>>;
|
|
164
138
|
$insert: InsertType<Prettify<InferArrayElement<TShape>>>;
|
|
139
|
+
$insertMany: (payload: InferArrayElement<TShape>[]) => void;
|
|
165
140
|
$cut: CutFunctionType<TShape>;
|
|
166
141
|
$cutSelected: () => void;
|
|
167
142
|
$cutByValue: (value: string | number | boolean) => void;
|
|
@@ -247,12 +222,20 @@ export type InsertTypeObj<T> = (payload: InsertParams<T>) => void;
|
|
|
247
222
|
|
|
248
223
|
type EffectFunction<T, R> = (state: T, deps: any[]) => R;
|
|
249
224
|
export type EndType<T, IsArrayElement = false> = {
|
|
225
|
+
$getPluginMetaData: (pluginName: string) => Record<string, any>;
|
|
226
|
+
$addPluginMetaData: (key: string, data: Record<string, any>) => void;
|
|
227
|
+
$removePluginMetaData: (key: string) => void;
|
|
228
|
+
$setOptions: (options: OptionsType<T>) => void;
|
|
229
|
+
|
|
250
230
|
$addZodValidation: (
|
|
251
231
|
errors: ValidationError[],
|
|
252
232
|
source?: 'client' | 'sync_engine' | 'api'
|
|
253
233
|
) => void;
|
|
254
234
|
$clearZodValidation: (paths?: string[]) => void;
|
|
255
|
-
$applyOperation: (
|
|
235
|
+
$applyOperation: (
|
|
236
|
+
operation: UpdateTypeDetail,
|
|
237
|
+
metaData?: Record<string, any>
|
|
238
|
+
) => void;
|
|
256
239
|
$applyJsonPatch: (patches: any[]) => void;
|
|
257
240
|
$update: UpdateType<T>;
|
|
258
241
|
$_path: string[];
|
|
@@ -273,7 +256,14 @@ export type EndType<T, IsArrayElement = false> = {
|
|
|
273
256
|
$isSelected: boolean;
|
|
274
257
|
$setSelected: (value: boolean) => void;
|
|
275
258
|
$toggleSelected: () => void;
|
|
276
|
-
$
|
|
259
|
+
$formInput: {
|
|
260
|
+
setDisabled: (isDisabled: boolean) => void;
|
|
261
|
+
focus: () => void;
|
|
262
|
+
blur: () => void;
|
|
263
|
+
scrollIntoView: (options?: ScrollIntoViewOptions) => void;
|
|
264
|
+
click: () => void;
|
|
265
|
+
selectText: () => void;
|
|
266
|
+
};
|
|
277
267
|
$removeStorage: () => void;
|
|
278
268
|
$sync: () => void;
|
|
279
269
|
$validationWrapper: ({
|
|
@@ -295,7 +285,7 @@ export type StateObject<T> = (T extends any[]
|
|
|
295
285
|
: never) &
|
|
296
286
|
EndType<T, true> & {
|
|
297
287
|
$toggle: T extends boolean ? () => void : never;
|
|
298
|
-
|
|
288
|
+
|
|
299
289
|
$_componentId: string | null;
|
|
300
290
|
$getComponents: () => ComponentsType;
|
|
301
291
|
|
|
@@ -303,6 +293,7 @@ export type StateObject<T> = (T extends any[]
|
|
|
303
293
|
$updateInitialState: (newState: T | null) => {
|
|
304
294
|
fetchId: (field: keyof T) => string | number;
|
|
305
295
|
};
|
|
296
|
+
$initializeAndMergeShadowState: (newState: any | null) => void;
|
|
306
297
|
$_isLoading: boolean;
|
|
307
298
|
$_serverState: T;
|
|
308
299
|
$revertToInitialState: (obj?: { validationKey?: string }) => T;
|
|
@@ -329,10 +320,13 @@ type EffectiveSetStateArg<
|
|
|
329
320
|
? InsertParams<InferArrayElement<T>>
|
|
330
321
|
: never
|
|
331
322
|
: UpdateArg<T>;
|
|
332
|
-
type UpdateOptions = {
|
|
333
|
-
updateType: 'insert' | 'cut' | 'update';
|
|
334
323
|
|
|
335
|
-
|
|
324
|
+
type UpdateOptions = {
|
|
325
|
+
updateType: 'insert' | 'cut' | 'update' | 'insert_many';
|
|
326
|
+
validationTrigger?: 'onBlur' | 'onChange' | 'programmatic';
|
|
327
|
+
itemId?: string;
|
|
328
|
+
index?: number;
|
|
329
|
+
metaData?: Record<string, any>;
|
|
336
330
|
};
|
|
337
331
|
type EffectiveSetState<TStateObject> = (
|
|
338
332
|
newStateOrFunction:
|
|
@@ -340,21 +334,23 @@ type EffectiveSetState<TStateObject> = (
|
|
|
340
334
|
| EffectiveSetStateArg<TStateObject, 'insert'>
|
|
341
335
|
| null,
|
|
342
336
|
path: string[],
|
|
343
|
-
updateObj: UpdateOptions,
|
|
337
|
+
updateObj: UpdateOptions, // Now includes itemId
|
|
344
338
|
validationKey?: string
|
|
345
339
|
) => void;
|
|
346
340
|
|
|
347
341
|
export type UpdateTypeDetail = {
|
|
348
342
|
timeStamp: number;
|
|
349
343
|
stateKey: string;
|
|
350
|
-
updateType: 'update' | 'insert' | 'cut';
|
|
344
|
+
updateType: 'update' | 'insert' | 'cut' | 'insert_many';
|
|
351
345
|
path: string[];
|
|
352
346
|
status: 'new' | 'sent' | 'synced';
|
|
353
347
|
oldValue: any;
|
|
354
348
|
newValue: any;
|
|
355
349
|
userId?: number;
|
|
356
|
-
|
|
357
|
-
|
|
350
|
+
|
|
351
|
+
itemId?: string;
|
|
352
|
+
insertAfterId?: string;
|
|
353
|
+
metaData?: Record<string, any>;
|
|
358
354
|
};
|
|
359
355
|
export type ReactivityUnion = 'none' | 'component' | 'deps' | 'all';
|
|
360
356
|
export type ReactivityType =
|
|
@@ -365,12 +361,7 @@ export type ReactivityType =
|
|
|
365
361
|
| Array<Prettify<'none' | 'component' | 'deps' | 'all'>>;
|
|
366
362
|
|
|
367
363
|
// Define the return type of the sync hook locally
|
|
368
|
-
|
|
369
|
-
updateState: (data: { operation: any }) => void;
|
|
370
|
-
connected: boolean;
|
|
371
|
-
clientId: string | null;
|
|
372
|
-
subscribers: string[];
|
|
373
|
-
};
|
|
364
|
+
|
|
374
365
|
type ValidationOptionsType = {
|
|
375
366
|
key?: string;
|
|
376
367
|
zodSchemaV3?: z3.ZodType<any, any, any>;
|
|
@@ -379,7 +370,7 @@ type ValidationOptionsType = {
|
|
|
379
370
|
onChange?: 'error' | 'warning';
|
|
380
371
|
blockSync?: boolean;
|
|
381
372
|
};
|
|
382
|
-
|
|
373
|
+
|
|
383
374
|
type SyncOptionsType<TApiParams> = {
|
|
384
375
|
apiParams: TApiParams;
|
|
385
376
|
stateKey?: string;
|
|
@@ -390,12 +381,23 @@ type SyncOptionsType<TApiParams> = {
|
|
|
390
381
|
connect?: boolean;
|
|
391
382
|
inMemoryState?: boolean;
|
|
392
383
|
};
|
|
393
|
-
|
|
384
|
+
|
|
385
|
+
export type CreateStateOptionsType<
|
|
386
|
+
T extends unknown = unknown,
|
|
387
|
+
TPlugins extends readonly CogsPlugin<any, any, any, any, any>[] = [],
|
|
388
|
+
> = {
|
|
389
|
+
formElements?: FormsElementsType<T, TPlugins>;
|
|
390
|
+
validation?: ValidationOptionsType;
|
|
391
|
+
plugins?: TPlugins;
|
|
392
|
+
};
|
|
393
|
+
export type OptionsType<
|
|
394
|
+
T extends unknown = unknown,
|
|
395
|
+
TApiParams = never,
|
|
396
|
+
> = CreateStateOptionsType & {
|
|
394
397
|
log?: boolean;
|
|
395
398
|
componentId?: string;
|
|
396
399
|
syncOptions?: SyncOptionsType<TApiParams>;
|
|
397
400
|
|
|
398
|
-
validation?: ValidationOptionsType;
|
|
399
401
|
serverState?: {
|
|
400
402
|
id?: string | number;
|
|
401
403
|
data?: T;
|
|
@@ -424,12 +426,10 @@ export type OptionsType<T extends unknown = unknown, TApiParams = never> = {
|
|
|
424
426
|
};
|
|
425
427
|
middleware?: ({ update }: { update: UpdateTypeDetail }) => void;
|
|
426
428
|
|
|
427
|
-
modifyState?: (state: T) => T;
|
|
428
429
|
localStorage?: {
|
|
429
430
|
key: string | ((state: T) => string);
|
|
430
431
|
onChange?: (state: T) => void;
|
|
431
432
|
};
|
|
432
|
-
formElements?: FormsElementsType<T>;
|
|
433
433
|
|
|
434
434
|
reactiveDeps?: (state: T) => any[] | true;
|
|
435
435
|
reactiveType?: ReactivityType;
|
|
@@ -440,14 +440,11 @@ export type OptionsType<T extends unknown = unknown, TApiParams = never> = {
|
|
|
440
440
|
dependencies?: any[];
|
|
441
441
|
};
|
|
442
442
|
|
|
443
|
-
export type
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
type FormsElementsType<T> = {
|
|
443
|
+
export type FormsElementsType<
|
|
444
|
+
TState,
|
|
445
|
+
TPlugins extends readonly CogsPlugin<any, any, any, any, any>[] = [],
|
|
446
|
+
> = {
|
|
447
|
+
// These optional, built-in wrappers are unchanged.
|
|
451
448
|
validation?: (options: {
|
|
452
449
|
children: React.ReactNode;
|
|
453
450
|
status: ValidationStatus;
|
|
@@ -455,28 +452,32 @@ type FormsElementsType<T> = {
|
|
|
455
452
|
hasErrors: boolean;
|
|
456
453
|
hasWarnings: boolean;
|
|
457
454
|
allErrors: ValidationError[];
|
|
458
|
-
|
|
459
455
|
path: string[];
|
|
460
456
|
message?: string;
|
|
461
|
-
getData?: () =>
|
|
457
|
+
getData?: () => TState;
|
|
462
458
|
}) => React.ReactNode;
|
|
463
|
-
syncRender?: (options:
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
[
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
459
|
+
syncRender?: (options: {
|
|
460
|
+
children: React.ReactNode;
|
|
461
|
+
time: number;
|
|
462
|
+
data?: TState;
|
|
463
|
+
key?: string;
|
|
464
|
+
}) => React.ReactNode;
|
|
465
|
+
} & {
|
|
466
|
+
// For each plugin `P` in the TPlugins array...
|
|
467
|
+
[P in TPlugins[number] as P['name']]?: P['formWrapper'] extends (
|
|
468
|
+
// ...check if its `formWrapper` property is a function...
|
|
469
|
+
arg: any
|
|
470
|
+
) => any
|
|
471
|
+
? // ...if it is, infer the type of its FIRST PARAMETER. This is the key.
|
|
472
|
+
Parameters<P['formWrapper']>[0]
|
|
473
|
+
: // Otherwise, the type is invalid.
|
|
474
|
+
never;
|
|
479
475
|
};
|
|
476
|
+
export type CogsInitialState<T> =
|
|
477
|
+
| {
|
|
478
|
+
initialState: T;
|
|
479
|
+
}
|
|
480
|
+
| CreateStateOptionsType<T>;
|
|
480
481
|
|
|
481
482
|
export type TransformedStateType<T> = {
|
|
482
483
|
[P in keyof T]: T[P] extends CogsInitialState<infer U> ? U : T[P];
|
|
@@ -489,6 +490,7 @@ const {
|
|
|
489
490
|
setShadowMetadata,
|
|
490
491
|
getShadowValue,
|
|
491
492
|
initializeShadowState,
|
|
493
|
+
initializeAndMergeShadowState,
|
|
492
494
|
updateShadowAtPath,
|
|
493
495
|
insertShadowArrayElement,
|
|
494
496
|
insertManyShadowArrayElements,
|
|
@@ -503,8 +505,14 @@ const {
|
|
|
503
505
|
clearSelectedIndex,
|
|
504
506
|
getSyncInfo,
|
|
505
507
|
notifyPathSubscribers,
|
|
508
|
+
getPluginMetaDataMap,
|
|
509
|
+
setPluginMetaData,
|
|
510
|
+
removePluginMetaData,
|
|
506
511
|
// Note: The old functions are no longer imported under their original names
|
|
507
512
|
} = getGlobalStore.getState();
|
|
513
|
+
|
|
514
|
+
const { notifyUpdate } = pluginStore.getState();
|
|
515
|
+
|
|
508
516
|
function getArrayData(stateKey: string, path: string[], meta?: MetaData) {
|
|
509
517
|
const shadowMeta = getShadowMetadata(stateKey, path);
|
|
510
518
|
const isArray = !!shadowMeta?.arrayKeys;
|
|
@@ -547,10 +555,21 @@ function findArrayItem(
|
|
|
547
555
|
function setAndMergeOptions(stateKey: string, newOptions: OptionsType<any>) {
|
|
548
556
|
const initialOptions = getInitialOptions(stateKey as string) || {};
|
|
549
557
|
|
|
550
|
-
|
|
558
|
+
const mergedOptions = {
|
|
551
559
|
...initialOptions,
|
|
552
560
|
...newOptions,
|
|
553
|
-
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// FIX: Apply the same default onBlur logic here too
|
|
564
|
+
if (
|
|
565
|
+
(mergedOptions.validation?.zodSchemaV4 ||
|
|
566
|
+
mergedOptions.validation?.zodSchemaV3) &&
|
|
567
|
+
!mergedOptions.validation?.onBlur
|
|
568
|
+
) {
|
|
569
|
+
mergedOptions.validation.onBlur = 'error';
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
setInitialStateOptions(stateKey as string, mergedOptions);
|
|
554
573
|
}
|
|
555
574
|
|
|
556
575
|
function setOptions<StateKey, Opt>({
|
|
@@ -565,28 +584,23 @@ function setOptions<StateKey, Opt>({
|
|
|
565
584
|
const initialOptions = getInitialOptions(stateKey as string) || {};
|
|
566
585
|
const initialOptionsPartState = initialOptionsPart[stateKey as string] || {};
|
|
567
586
|
|
|
568
|
-
// Start with the base options
|
|
569
587
|
let mergedOptions = { ...initialOptionsPartState, ...initialOptions };
|
|
570
588
|
let needToAdd = false;
|
|
571
589
|
|
|
572
590
|
if (options) {
|
|
573
|
-
// A function to recursively merge properties
|
|
574
591
|
const deepMerge = (target: any, source: any) => {
|
|
575
592
|
for (const key in source) {
|
|
576
593
|
if (source.hasOwnProperty(key)) {
|
|
577
|
-
// If the property is an object (and not an array), recurse
|
|
578
594
|
if (
|
|
579
595
|
source[key] instanceof Object &&
|
|
580
596
|
!Array.isArray(source[key]) &&
|
|
581
597
|
target[key] instanceof Object
|
|
582
598
|
) {
|
|
583
|
-
// Check for changes before merging to set `needToAdd`
|
|
584
599
|
if (!isDeepEqual(target[key], source[key])) {
|
|
585
600
|
deepMerge(target[key], source[key]);
|
|
586
601
|
needToAdd = true;
|
|
587
602
|
}
|
|
588
603
|
} else {
|
|
589
|
-
// Overwrite if the value is different
|
|
590
604
|
if (target[key] !== source[key]) {
|
|
591
605
|
target[key] = source[key];
|
|
592
606
|
needToAdd = true;
|
|
@@ -597,78 +611,107 @@ function setOptions<StateKey, Opt>({
|
|
|
597
611
|
return target;
|
|
598
612
|
};
|
|
599
613
|
|
|
600
|
-
// Perform a deep merge
|
|
601
614
|
mergedOptions = deepMerge(mergedOptions, options);
|
|
602
615
|
}
|
|
603
616
|
|
|
604
|
-
//
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
(!options || !options.hasOwnProperty('syncOptions'))
|
|
608
|
-
) {
|
|
609
|
-
needToAdd = true;
|
|
610
|
-
}
|
|
611
|
-
if (
|
|
612
|
-
(mergedOptions.validation && mergedOptions?.validation?.zodSchemaV4) ||
|
|
613
|
-
mergedOptions?.validation?.zodSchemaV3
|
|
614
|
-
) {
|
|
615
|
-
// Only set default if onBlur wasn't explicitly provided
|
|
616
|
-
const wasOnBlurProvided =
|
|
617
|
+
// Set default onBlur
|
|
618
|
+
if (mergedOptions.validation) {
|
|
619
|
+
const onBlurProvided =
|
|
617
620
|
options?.validation?.hasOwnProperty('onBlur') ||
|
|
618
|
-
initialOptions?.validation?.hasOwnProperty('onBlur')
|
|
621
|
+
initialOptions?.validation?.hasOwnProperty('onBlur') ||
|
|
622
|
+
initialOptionsPartState?.validation?.hasOwnProperty('onBlur');
|
|
619
623
|
|
|
620
|
-
if (!
|
|
621
|
-
mergedOptions.validation.onBlur = 'error';
|
|
624
|
+
if (!onBlurProvided) {
|
|
625
|
+
mergedOptions.validation.onBlur = 'error';
|
|
626
|
+
needToAdd = true;
|
|
622
627
|
}
|
|
623
628
|
}
|
|
629
|
+
|
|
624
630
|
if (needToAdd) {
|
|
625
631
|
setInitialStateOptions(stateKey as string, mergedOptions);
|
|
632
|
+
|
|
633
|
+
// NEW: Update type info if validation schema was added
|
|
634
|
+
const hadSchema =
|
|
635
|
+
initialOptions?.validation?.zodSchemaV4 ||
|
|
636
|
+
initialOptions?.validation?.zodSchemaV3;
|
|
637
|
+
const hasNewSchemaV4 =
|
|
638
|
+
mergedOptions.validation?.zodSchemaV4 &&
|
|
639
|
+
!initialOptions?.validation?.zodSchemaV4;
|
|
640
|
+
const hasNewSchemaV3 =
|
|
641
|
+
mergedOptions.validation?.zodSchemaV3 &&
|
|
642
|
+
!initialOptions?.validation?.zodSchemaV3;
|
|
643
|
+
|
|
644
|
+
if (!hadSchema && (hasNewSchemaV4 || hasNewSchemaV3)) {
|
|
645
|
+
if (hasNewSchemaV4) {
|
|
646
|
+
updateShadowTypeInfo(
|
|
647
|
+
stateKey as string,
|
|
648
|
+
mergedOptions.validation.zodSchemaV4,
|
|
649
|
+
'zod4'
|
|
650
|
+
);
|
|
651
|
+
} else if (hasNewSchemaV3) {
|
|
652
|
+
updateShadowTypeInfo(
|
|
653
|
+
stateKey as string,
|
|
654
|
+
mergedOptions.validation.zodSchemaV3,
|
|
655
|
+
'zod3'
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Notify components to re-render with updated validation
|
|
660
|
+
notifyComponents(stateKey as string);
|
|
661
|
+
}
|
|
626
662
|
}
|
|
627
|
-
}
|
|
628
663
|
|
|
629
|
-
|
|
664
|
+
return mergedOptions;
|
|
665
|
+
}
|
|
666
|
+
export function addStateOptions<T>(
|
|
630
667
|
initialState: T,
|
|
631
|
-
|
|
668
|
+
options: CreateStateOptionsType<T>
|
|
632
669
|
) {
|
|
633
|
-
return {
|
|
670
|
+
return {
|
|
671
|
+
...options,
|
|
672
|
+
initialState,
|
|
673
|
+
_addStateOptions: true,
|
|
674
|
+
};
|
|
634
675
|
}
|
|
676
|
+
export type PluginData = {
|
|
677
|
+
plugin: CogsPlugin<any, any, any, any, any>;
|
|
678
|
+
options: any;
|
|
679
|
+
hookData?: any;
|
|
680
|
+
};
|
|
635
681
|
|
|
636
|
-
|
|
637
|
-
type SetCogsOptionsFunc<T extends Record<string, any>> = <
|
|
638
|
-
StateKey extends keyof TransformedStateType<T>,
|
|
639
|
-
>(
|
|
640
|
-
stateKey: StateKey,
|
|
641
|
-
options: OptionsType<TransformedStateType<T>[StateKey]>
|
|
642
|
-
) => void;
|
|
682
|
+
////////////////////////////////
|
|
643
683
|
|
|
644
|
-
export const createCogsState = <
|
|
684
|
+
export const createCogsState = <
|
|
685
|
+
State extends Record<string, unknown>,
|
|
686
|
+
const TPlugins extends readonly CogsPlugin<string, any, any, any, any>[] = [],
|
|
687
|
+
>(
|
|
645
688
|
initialState: State,
|
|
646
689
|
opt?: {
|
|
647
|
-
|
|
690
|
+
plugins?: TPlugins;
|
|
691
|
+
formElements?: FormsElementsType<State, TPlugins>;
|
|
648
692
|
validation?: ValidationOptionsType;
|
|
649
|
-
__fromSyncSchema?: boolean;
|
|
650
|
-
__syncNotifications?: Record<string, Function>;
|
|
651
|
-
__apiParamsMap?: Record<string, any>;
|
|
652
|
-
__useSync?: UseSyncType<State>;
|
|
653
|
-
__syncSchemas?: Record<string, any>;
|
|
654
693
|
}
|
|
655
694
|
) => {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
695
|
+
type PluginOptions = {
|
|
696
|
+
[K in TPlugins[number] as K['name']]?: K extends CogsPlugin<
|
|
697
|
+
string,
|
|
698
|
+
infer O,
|
|
699
|
+
any,
|
|
700
|
+
any,
|
|
701
|
+
any
|
|
702
|
+
>
|
|
703
|
+
? O
|
|
704
|
+
: never;
|
|
705
|
+
};
|
|
659
706
|
|
|
660
|
-
if (opt?.
|
|
661
|
-
|
|
662
|
-
.getState()
|
|
663
|
-
.setInitialStateOptions('__notifications', opt.__syncNotifications);
|
|
707
|
+
if (opt?.plugins) {
|
|
708
|
+
pluginStore.getState().setRegisteredPlugins(opt.plugins as any);
|
|
664
709
|
}
|
|
665
710
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
.getState()
|
|
669
|
-
.setInitialStateOptions('__apiParamsMap', opt.__apiParamsMap);
|
|
670
|
-
}
|
|
711
|
+
const [statePart, initialOptionsPart] =
|
|
712
|
+
transformStateFunc<State>(initialState);
|
|
671
713
|
|
|
714
|
+
// FIX: Store options INCLUDING validation for each state key
|
|
672
715
|
Object.keys(statePart).forEach((key) => {
|
|
673
716
|
let existingOptions = initialOptionsPart[key] || {};
|
|
674
717
|
|
|
@@ -683,61 +726,77 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
683
726
|
};
|
|
684
727
|
}
|
|
685
728
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
729
|
+
mergedOptions.validation = {
|
|
730
|
+
onBlur: 'error',
|
|
731
|
+
...opt?.validation,
|
|
732
|
+
...(existingOptions.validation || {}),
|
|
733
|
+
};
|
|
691
734
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
if (opt?.__syncSchemas?.[key]?.schemas?.validation) {
|
|
697
|
-
mergedOptions.validation = {
|
|
698
|
-
zodSchemaV4: opt.__syncSchemas[key].schemas.validation,
|
|
699
|
-
...existingOptions.validation,
|
|
700
|
-
};
|
|
735
|
+
if (opt?.validation?.key && !existingOptions.validation?.key) {
|
|
736
|
+
mergedOptions.validation.key = `${opt.validation.key}.${key}`;
|
|
701
737
|
}
|
|
702
|
-
if (Object.keys(mergedOptions).length > 0) {
|
|
703
|
-
const existingGlobalOptions = getInitialOptions(key);
|
|
704
738
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
setInitialStateOptions(key, {
|
|
739
|
+
const existingGlobalOptions = getInitialOptions(key);
|
|
740
|
+
|
|
741
|
+
const finalOptions = existingGlobalOptions
|
|
742
|
+
? {
|
|
710
743
|
...existingGlobalOptions,
|
|
711
744
|
...mergedOptions,
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
745
|
+
formElements: {
|
|
746
|
+
...existingGlobalOptions.formElements,
|
|
747
|
+
...mergedOptions.formElements,
|
|
748
|
+
},
|
|
749
|
+
validation: {
|
|
750
|
+
...existingGlobalOptions.validation,
|
|
751
|
+
...mergedOptions.validation,
|
|
752
|
+
},
|
|
753
|
+
}
|
|
754
|
+
: mergedOptions;
|
|
755
|
+
|
|
756
|
+
setInitialStateOptions(key, finalOptions);
|
|
715
757
|
});
|
|
716
758
|
|
|
717
759
|
Object.keys(statePart).forEach((key) => {
|
|
718
760
|
initializeShadowState(key, statePart[key]);
|
|
719
761
|
});
|
|
720
|
-
|
|
762
|
+
type Prettify<T> = {
|
|
763
|
+
[K in keyof T]: T[K];
|
|
764
|
+
} & {};
|
|
721
765
|
type StateKeys = keyof typeof statePart;
|
|
722
766
|
|
|
723
767
|
const useCogsState = <StateKey extends StateKeys>(
|
|
724
768
|
stateKey: StateKey,
|
|
725
|
-
options?: Prettify<
|
|
769
|
+
options?: Prettify<
|
|
770
|
+
OptionsType<(typeof statePart)[StateKey], never> & {
|
|
771
|
+
[PName in keyof PluginOptions]?: PluginOptions[PName] extends infer P
|
|
772
|
+
? P extends Record<string, any>
|
|
773
|
+
? {
|
|
774
|
+
[K in keyof P]: P[K] extends { __key: 'keyed'; map: infer TMap }
|
|
775
|
+
? StateKey extends keyof TMap
|
|
776
|
+
? TMap[StateKey]
|
|
777
|
+
: never
|
|
778
|
+
: P[K];
|
|
779
|
+
}
|
|
780
|
+
: P
|
|
781
|
+
: never;
|
|
782
|
+
}
|
|
783
|
+
>
|
|
726
784
|
) => {
|
|
727
785
|
const [componentId] = useState(options?.componentId ?? uuidv4());
|
|
728
786
|
|
|
729
|
-
setOptions({
|
|
787
|
+
const currentOptions = setOptions({
|
|
730
788
|
stateKey,
|
|
731
789
|
options,
|
|
732
790
|
initialOptionsPart,
|
|
733
791
|
});
|
|
792
|
+
|
|
793
|
+
const optionsRef = useRef(currentOptions);
|
|
794
|
+
optionsRef.current = currentOptions;
|
|
795
|
+
|
|
734
796
|
const thiState =
|
|
735
797
|
getShadowValue(stateKey as string, []) || statePart[stateKey as string];
|
|
736
|
-
const partialState = options?.modifyState
|
|
737
|
-
? options.modifyState(thiState)
|
|
738
|
-
: thiState;
|
|
739
798
|
|
|
740
|
-
const updater = useCogsStateFn<(typeof statePart)[StateKey]>(
|
|
799
|
+
const updater = useCogsStateFn<(typeof statePart)[StateKey]>(thiState, {
|
|
741
800
|
stateKey: stateKey as string,
|
|
742
801
|
syncUpdate: options?.syncUpdate,
|
|
743
802
|
componentId,
|
|
@@ -748,14 +807,30 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
748
807
|
defaultState: options?.defaultState as any,
|
|
749
808
|
dependencies: options?.dependencies,
|
|
750
809
|
serverState: options?.serverState,
|
|
751
|
-
syncOptions: options?.syncOptions,
|
|
752
|
-
__useSync: opt?.__useSync as UseSyncType<(typeof statePart)[StateKey]>,
|
|
753
810
|
});
|
|
754
811
|
|
|
812
|
+
useEffect(() => {
|
|
813
|
+
if (options) {
|
|
814
|
+
pluginStore
|
|
815
|
+
.getState()
|
|
816
|
+
.setPluginOptionsForState(stateKey as string, options);
|
|
817
|
+
}
|
|
818
|
+
}, [stateKey, options]);
|
|
819
|
+
useEffect(() => {
|
|
820
|
+
console.log('adding handler 1', stateKey, updater);
|
|
821
|
+
pluginStore
|
|
822
|
+
.getState()
|
|
823
|
+
.stateHandlers.set(stateKey as string, updater as any);
|
|
824
|
+
|
|
825
|
+
return () => {
|
|
826
|
+
pluginStore.getState().stateHandlers.delete(stateKey as string);
|
|
827
|
+
};
|
|
828
|
+
}, [stateKey, updater]);
|
|
829
|
+
|
|
755
830
|
return updater;
|
|
756
831
|
};
|
|
757
832
|
|
|
758
|
-
function
|
|
833
|
+
function setCogsOptionsByKey<StateKey extends StateKeys>(
|
|
759
834
|
stateKey: StateKey,
|
|
760
835
|
options: OptionsType<(typeof statePart)[StateKey]>
|
|
761
836
|
) {
|
|
@@ -768,109 +843,53 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
768
843
|
notifyComponents(stateKey as string);
|
|
769
844
|
}
|
|
770
845
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
> & {
|
|
788
|
-
syncOptions: Prettify<SyncOptionsType<TApiParamsMap[StateKey]>>;
|
|
789
|
-
}
|
|
790
|
-
>
|
|
791
|
-
: Prettify<OptionsType<TransformedStateType<T>[StateKey]>>
|
|
792
|
-
) => StateObject<TransformedStateType<T>[StateKey]>;
|
|
793
|
-
|
|
794
|
-
// Update CogsApi to default to never instead of Record<string, never>
|
|
795
|
-
type CogsApi<
|
|
796
|
-
T extends Record<string, any>,
|
|
797
|
-
TApiParamsMap extends Record<string, any> = never,
|
|
798
|
-
> = {
|
|
799
|
-
useCogsState: UseCogsStateHook<T, TApiParamsMap>;
|
|
800
|
-
setCogsOptions: SetCogsOptionsFunc<T>;
|
|
801
|
-
};
|
|
802
|
-
type GetParamType<SchemaEntry> = SchemaEntry extends {
|
|
803
|
-
api?: { queryData?: { _paramType?: infer P } };
|
|
804
|
-
}
|
|
805
|
-
? P
|
|
806
|
-
: never;
|
|
807
|
-
|
|
808
|
-
export function createCogsStateFromSync<
|
|
809
|
-
TSyncSchema extends {
|
|
810
|
-
schemas: Record<
|
|
811
|
-
string,
|
|
812
|
-
{
|
|
813
|
-
schemas: { defaults: any };
|
|
814
|
-
relations?: any;
|
|
815
|
-
api?: {
|
|
816
|
-
queryData?: any;
|
|
846
|
+
function setCogsFormElements(
|
|
847
|
+
formElements: FormsElementsType<State, TPlugins>
|
|
848
|
+
) {
|
|
849
|
+
// Get the current list of registered plugins from the store.
|
|
850
|
+
const currentPlugins = pluginStore.getState().registeredPlugins;
|
|
851
|
+
|
|
852
|
+
// Create a new array by mapping over the current plugins.
|
|
853
|
+
// This is crucial for immutability and ensuring Zustand detects the change.
|
|
854
|
+
const updatedPlugins = currentPlugins.map((plugin) => {
|
|
855
|
+
// Check if the formElements object has a wrapper for this specific plugin by name.
|
|
856
|
+
if (formElements.hasOwnProperty(plugin.name)) {
|
|
857
|
+
// If it does, return a *new* plugin object.
|
|
858
|
+
// Spread the existing plugin properties and add/overwrite the formWrapper.
|
|
859
|
+
return {
|
|
860
|
+
...plugin,
|
|
861
|
+
formWrapper: formElements[plugin.name as keyof typeof formElements],
|
|
817
862
|
};
|
|
818
|
-
[key: string]: any;
|
|
819
863
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
>(
|
|
824
|
-
syncSchema: TSyncSchema,
|
|
825
|
-
useSync: UseSyncType<any>
|
|
826
|
-
): CogsApi<
|
|
827
|
-
{
|
|
828
|
-
[K in keyof TSyncSchema['schemas']]: TSyncSchema['schemas'][K]['relations'] extends object
|
|
829
|
-
? TSyncSchema['schemas'][K] extends {
|
|
830
|
-
schemas: { defaults: infer D };
|
|
831
|
-
}
|
|
832
|
-
? D
|
|
833
|
-
: TSyncSchema['schemas'][K]['schemas']['defaults']
|
|
834
|
-
: TSyncSchema['schemas'][K]['schemas']['defaults'];
|
|
835
|
-
},
|
|
836
|
-
{
|
|
837
|
-
[K in keyof TSyncSchema['schemas']]: GetParamType<
|
|
838
|
-
TSyncSchema['schemas'][K]
|
|
839
|
-
>;
|
|
840
|
-
}
|
|
841
|
-
> {
|
|
842
|
-
const schemas = syncSchema.schemas;
|
|
843
|
-
const initialState: any = {};
|
|
844
|
-
const apiParamsMap: any = {};
|
|
845
|
-
|
|
846
|
-
// Extract defaultValues AND apiParams from each entry
|
|
847
|
-
for (const key in schemas) {
|
|
848
|
-
const entry = schemas[key];
|
|
849
|
-
|
|
850
|
-
// Check if we have relations and thus view defaults
|
|
851
|
-
if (entry?.relations && entry?.schemas?.defaults) {
|
|
852
|
-
// Use the view defaults when relations are present
|
|
853
|
-
initialState[key] = entry.schemas.defaults;
|
|
854
|
-
} else {
|
|
855
|
-
// Fall back to regular defaultValues
|
|
856
|
-
initialState[key] = entry?.schemas?.defaults || {};
|
|
857
|
-
}
|
|
858
|
-
console.log('initialState', initialState);
|
|
864
|
+
// If there's no new wrapper for this plugin, return the original object.
|
|
865
|
+
return plugin;
|
|
866
|
+
});
|
|
859
867
|
|
|
860
|
-
//
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
868
|
+
// Use the store's dedicated setter function to update the registered plugins list.
|
|
869
|
+
// This will trigger a state update that components listening to the store will react to.
|
|
870
|
+
pluginStore.getState().setRegisteredPlugins(updatedPlugins as any);
|
|
871
|
+
|
|
872
|
+
// For good measure and consistency, we should still update the formElements
|
|
873
|
+
// in the initial options, in case any other part of the system relies on it.
|
|
874
|
+
const allStateKeys = Object.keys(statePart);
|
|
875
|
+
allStateKeys.forEach((key) => {
|
|
876
|
+
const existingOptions = getInitialOptions(key) || {};
|
|
877
|
+
const finalOptions = {
|
|
878
|
+
...existingOptions,
|
|
879
|
+
formElements: {
|
|
880
|
+
...(existingOptions.formElements || {}),
|
|
881
|
+
...formElements,
|
|
882
|
+
},
|
|
883
|
+
};
|
|
884
|
+
setInitialStateOptions(key, finalOptions);
|
|
885
|
+
});
|
|
864
886
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
__syncSchemas: schemas,
|
|
872
|
-
}) as any;
|
|
873
|
-
}
|
|
887
|
+
return {
|
|
888
|
+
useCogsState,
|
|
889
|
+
setCogsOptionsByKey,
|
|
890
|
+
setCogsFormElements,
|
|
891
|
+
};
|
|
892
|
+
};
|
|
874
893
|
|
|
875
894
|
const saveToLocalStorage = <T,>(
|
|
876
895
|
state: T,
|
|
@@ -1042,35 +1061,14 @@ let isFlushScheduled = false;
|
|
|
1042
1061
|
function scheduleFlush() {
|
|
1043
1062
|
if (!isFlushScheduled) {
|
|
1044
1063
|
isFlushScheduled = true;
|
|
1045
|
-
|
|
1064
|
+
console.log('Scheduling flush');
|
|
1065
|
+
queueMicrotask(() => {
|
|
1066
|
+
console.log('Actually flushing');
|
|
1067
|
+
flushQueue();
|
|
1068
|
+
});
|
|
1046
1069
|
}
|
|
1047
1070
|
}
|
|
1048
|
-
function handleUpdate(
|
|
1049
|
-
stateKey: string,
|
|
1050
|
-
path: string[],
|
|
1051
|
-
payload: any
|
|
1052
|
-
): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
|
|
1053
|
-
// ✅ FIX: Get the old value before the update.
|
|
1054
|
-
const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
|
|
1055
1071
|
|
|
1056
|
-
const newValue = isFunction(payload) ? payload(oldValue) : payload;
|
|
1057
|
-
|
|
1058
|
-
// ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
|
|
1059
|
-
// The manual loop has been removed.
|
|
1060
|
-
updateShadowAtPath(stateKey, path, newValue);
|
|
1061
|
-
|
|
1062
|
-
markAsDirty(stateKey, path, { bubble: true });
|
|
1063
|
-
|
|
1064
|
-
// Return the metadata of the node *after* the update.
|
|
1065
|
-
const newShadowMeta = getShadowMetadata(stateKey, path);
|
|
1066
|
-
|
|
1067
|
-
return {
|
|
1068
|
-
type: 'update',
|
|
1069
|
-
oldValue: oldValue,
|
|
1070
|
-
newValue,
|
|
1071
|
-
shadowMeta: newShadowMeta,
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
1072
|
// 2. Update signals
|
|
1075
1073
|
function updateSignals(shadowMeta: any, displayValue: any) {
|
|
1076
1074
|
if (!shadowMeta?.signals?.length) return;
|
|
@@ -1114,72 +1112,179 @@ function getComponentNotifications(
|
|
|
1114
1112
|
|
|
1115
1113
|
const componentsToNotify = new Set<any>();
|
|
1116
1114
|
|
|
1117
|
-
//
|
|
1118
|
-
|
|
1119
|
-
if (result.type === '
|
|
1120
|
-
//
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
componentsToNotify.add(component);
|
|
1115
|
+
// --- PASS 1: Notify specific subscribers based on update type ---
|
|
1116
|
+
|
|
1117
|
+
if (result.type === 'update') {
|
|
1118
|
+
// --- Bubble-up Notification ---
|
|
1119
|
+
// An update to `user.address.street` notifies listeners of `street`, `address`, and `user`.
|
|
1120
|
+
let currentPath = [...path];
|
|
1121
|
+
while (true) {
|
|
1122
|
+
const pathMeta = getShadowMetadata(stateKey, currentPath);
|
|
1123
|
+
|
|
1124
|
+
if (pathMeta?.pathComponents) {
|
|
1125
|
+
pathMeta.pathComponents.forEach((componentId: string) => {
|
|
1126
|
+
const component = rootMeta.components?.get(componentId);
|
|
1127
|
+
// NEW: Add component to the set instead of calling forceUpdate()
|
|
1128
|
+
if (component) {
|
|
1129
|
+
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1130
|
+
? component.reactiveType
|
|
1131
|
+
: [component.reactiveType || 'component'];
|
|
1132
|
+
if (!reactiveTypes.includes('none')) {
|
|
1133
|
+
componentsToNotify.add(component);
|
|
1134
|
+
}
|
|
1138
1135
|
}
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
if (currentPath.length === 0) break;
|
|
1140
|
+
currentPath.pop(); // Go up one level
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// --- Deep Object Change Notification ---
|
|
1144
|
+
// If the new value is an object, notify components subscribed to sub-paths that changed.
|
|
1145
|
+
if (
|
|
1146
|
+
result.newValue &&
|
|
1147
|
+
typeof result.newValue === 'object' &&
|
|
1148
|
+
!isArray(result.newValue)
|
|
1149
|
+
) {
|
|
1150
|
+
const changedSubPaths = getDifferences(result.newValue, result.oldValue);
|
|
1151
|
+
|
|
1152
|
+
changedSubPaths.forEach((subPathString: string) => {
|
|
1153
|
+
const subPath = subPathString.split('.');
|
|
1154
|
+
const fullSubPath = [...path, ...subPath];
|
|
1155
|
+
const subPathMeta = getShadowMetadata(stateKey, fullSubPath);
|
|
1156
|
+
|
|
1157
|
+
if (subPathMeta?.pathComponents) {
|
|
1158
|
+
subPathMeta.pathComponents.forEach((componentId: string) => {
|
|
1159
|
+
const component = rootMeta.components?.get(componentId);
|
|
1160
|
+
// NEW: Add component to the set
|
|
1161
|
+
if (component) {
|
|
1162
|
+
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1163
|
+
? component.reactiveType
|
|
1164
|
+
: [component.reactiveType || 'component'];
|
|
1165
|
+
if (!reactiveTypes.includes('none')) {
|
|
1166
|
+
componentsToNotify.add(component);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1139
1170
|
}
|
|
1140
1171
|
});
|
|
1141
1172
|
}
|
|
1173
|
+
} else if (
|
|
1174
|
+
result.type === 'insert' ||
|
|
1175
|
+
result.type === 'cut' ||
|
|
1176
|
+
result.type === 'insert_many'
|
|
1177
|
+
) {
|
|
1178
|
+
// This path is the array itself (e.g., ['pets'])
|
|
1179
|
+
const parentArrayPath = result.type === 'insert' ? path : path.slice(0, -1);
|
|
1180
|
+
|
|
1181
|
+
// --- FIX: ADD BUBBLE-UP LOGIC HERE ---
|
|
1182
|
+
// Start from the array's path and go up to the root
|
|
1183
|
+
let currentPath = [...parentArrayPath];
|
|
1184
|
+
while (true) {
|
|
1185
|
+
const pathMeta = getShadowMetadata(stateKey, currentPath);
|
|
1186
|
+
|
|
1187
|
+
if (pathMeta?.pathComponents) {
|
|
1188
|
+
pathMeta.pathComponents.forEach((componentId: string) => {
|
|
1189
|
+
const component = rootMeta.components?.get(componentId);
|
|
1190
|
+
if (component) {
|
|
1191
|
+
componentsToNotify.add(component);
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1142
1195
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1196
|
+
if (currentPath.length === 0) break;
|
|
1197
|
+
currentPath.pop(); // Go up one level
|
|
1198
|
+
}
|
|
1199
|
+
// --- END FIX ---
|
|
1145
1200
|
}
|
|
1146
1201
|
|
|
1147
1202
|
// --- PASS 2: Handle 'all' and 'deps' reactivity types ---
|
|
1148
1203
|
// Iterate over all components for this stateKey that haven't been notified yet.
|
|
1149
|
-
rootMeta.components.forEach((component, componentId) => {
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
});
|
|
1204
|
+
// rootMeta.components.forEach((component, componentId) => {
|
|
1205
|
+
// // If we've already added this component, skip it.
|
|
1206
|
+
// if (componentsToNotify.has(component)) {
|
|
1207
|
+
// return;
|
|
1208
|
+
// }
|
|
1209
|
+
|
|
1210
|
+
// const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1211
|
+
// ? component.reactiveType
|
|
1212
|
+
// : [component.reactiveType || 'component'];
|
|
1213
|
+
|
|
1214
|
+
// if (reactiveTypes.includes('all')) {
|
|
1215
|
+
// componentsToNotify.add(component);
|
|
1216
|
+
// } else if (reactiveTypes.includes('deps') && component.depsFunction) {
|
|
1217
|
+
// const currentState = getShadowValue(stateKey, []);
|
|
1218
|
+
// const newDeps = component.depsFunction(currentState);
|
|
1219
|
+
|
|
1220
|
+
// if (
|
|
1221
|
+
// newDeps === true ||
|
|
1222
|
+
// (Array.isArray(newDeps) && !isDeepEqual(component.prevDeps, newDeps))
|
|
1223
|
+
// ) {
|
|
1224
|
+
// component.prevDeps = newDeps as any; // Update the dependencies for the next check
|
|
1225
|
+
// componentsToNotify.add(component);
|
|
1226
|
+
// }
|
|
1227
|
+
// }
|
|
1228
|
+
// });
|
|
1174
1229
|
|
|
1175
1230
|
return componentsToNotify;
|
|
1176
1231
|
}
|
|
1177
1232
|
|
|
1233
|
+
function handleUpdate(
|
|
1234
|
+
stateKey: string,
|
|
1235
|
+
path: string[],
|
|
1236
|
+
payload: any
|
|
1237
|
+
): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
|
|
1238
|
+
// ✅ FIX: Get the old value before the update.
|
|
1239
|
+
const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
|
|
1240
|
+
|
|
1241
|
+
const newValue = isFunction(payload) ? payload(oldValue) : payload;
|
|
1242
|
+
|
|
1243
|
+
// ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
|
|
1244
|
+
// The manual loop has been removed.
|
|
1245
|
+
updateShadowAtPath(stateKey, path, newValue);
|
|
1246
|
+
|
|
1247
|
+
markAsDirty(stateKey, path, { bubble: true });
|
|
1248
|
+
|
|
1249
|
+
// Return the metadata of the node *after* the update.
|
|
1250
|
+
const newShadowMeta = getShadowMetadata(stateKey, path);
|
|
1251
|
+
|
|
1252
|
+
return {
|
|
1253
|
+
type: 'update',
|
|
1254
|
+
oldValue: oldValue,
|
|
1255
|
+
newValue,
|
|
1256
|
+
shadowMeta: newShadowMeta,
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
function handleInsertMany(
|
|
1260
|
+
stateKey: string,
|
|
1261
|
+
path: string[],
|
|
1262
|
+
payload: any[]
|
|
1263
|
+
): {
|
|
1264
|
+
type: 'insert_many';
|
|
1265
|
+
count: number;
|
|
1266
|
+
shadowMeta: any;
|
|
1267
|
+
path: string[];
|
|
1268
|
+
} {
|
|
1269
|
+
// Use the existing, optimized global store function to perform the state update
|
|
1270
|
+
insertManyShadowArrayElements(stateKey, path, payload);
|
|
1271
|
+
|
|
1272
|
+
markAsDirty(stateKey, path, { bubble: true });
|
|
1273
|
+
const updatedMeta = getShadowMetadata(stateKey, path);
|
|
1274
|
+
|
|
1275
|
+
return {
|
|
1276
|
+
type: 'insert_many',
|
|
1277
|
+
count: payload.length,
|
|
1278
|
+
shadowMeta: updatedMeta,
|
|
1279
|
+
path: path,
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1178
1282
|
function handleInsert(
|
|
1179
1283
|
stateKey: string,
|
|
1180
1284
|
path: string[],
|
|
1181
1285
|
payload: any,
|
|
1182
|
-
index?: number
|
|
1286
|
+
index?: number,
|
|
1287
|
+
itemId?: string
|
|
1183
1288
|
): {
|
|
1184
1289
|
type: 'insert';
|
|
1185
1290
|
newValue: any;
|
|
@@ -1189,19 +1294,28 @@ function handleInsert(
|
|
|
1189
1294
|
insertAfterId?: string;
|
|
1190
1295
|
} {
|
|
1191
1296
|
let newValue;
|
|
1297
|
+
|
|
1192
1298
|
if (isFunction(payload)) {
|
|
1193
1299
|
const { value: currentValue } = getScopedData(stateKey, path);
|
|
1194
|
-
newValue = payload({ state: currentValue
|
|
1300
|
+
newValue = payload({ state: currentValue });
|
|
1195
1301
|
} else {
|
|
1196
1302
|
newValue = payload;
|
|
1197
1303
|
}
|
|
1304
|
+
//console.time('insertShadowArrayElement');
|
|
1305
|
+
// Pass itemId to insertShadowArrayElement
|
|
1306
|
+
const actualItemId = insertShadowArrayElement(
|
|
1307
|
+
stateKey,
|
|
1308
|
+
path,
|
|
1309
|
+
newValue,
|
|
1310
|
+
index,
|
|
1311
|
+
itemId
|
|
1312
|
+
);
|
|
1313
|
+
//console.timeEnd('insertShadowArrayElement');
|
|
1198
1314
|
|
|
1199
|
-
const itemId = insertShadowArrayElement(stateKey, path, newValue, index);
|
|
1200
1315
|
markAsDirty(stateKey, path, { bubble: true });
|
|
1201
1316
|
|
|
1202
1317
|
const updatedMeta = getShadowMetadata(stateKey, path);
|
|
1203
1318
|
|
|
1204
|
-
// Find the ID that comes before this insertion point
|
|
1205
1319
|
let insertAfterId: string | undefined;
|
|
1206
1320
|
if (updatedMeta?.arrayKeys && index !== undefined && index > 0) {
|
|
1207
1321
|
insertAfterId = updatedMeta.arrayKeys[index - 1];
|
|
@@ -1211,11 +1325,12 @@ function handleInsert(
|
|
|
1211
1325
|
type: 'insert',
|
|
1212
1326
|
newValue,
|
|
1213
1327
|
shadowMeta: updatedMeta,
|
|
1214
|
-
path: path,
|
|
1215
|
-
itemId:
|
|
1328
|
+
path: path,
|
|
1329
|
+
itemId: actualItemId,
|
|
1216
1330
|
insertAfterId: insertAfterId,
|
|
1217
1331
|
};
|
|
1218
1332
|
}
|
|
1333
|
+
|
|
1219
1334
|
function handleCut(
|
|
1220
1335
|
stateKey: string,
|
|
1221
1336
|
path: string[]
|
|
@@ -1245,7 +1360,7 @@ function flushQueue() {
|
|
|
1245
1360
|
if (result.shadowMeta?.signals?.length > 0) {
|
|
1246
1361
|
signalUpdates.push({ shadowMeta: result.shadowMeta, displayValue });
|
|
1247
1362
|
}
|
|
1248
|
-
|
|
1363
|
+
// console.time('getComponentNotifications');
|
|
1249
1364
|
const componentNotifications = getComponentNotifications(
|
|
1250
1365
|
result.stateKey,
|
|
1251
1366
|
result.path,
|
|
@@ -1255,63 +1370,64 @@ function flushQueue() {
|
|
|
1255
1370
|
componentNotifications.forEach((component) => {
|
|
1256
1371
|
allComponentsToNotify.add(component);
|
|
1257
1372
|
});
|
|
1373
|
+
// console.timeEnd('getComponentNotifications');
|
|
1258
1374
|
}
|
|
1259
|
-
|
|
1375
|
+
//console.time('logs');
|
|
1260
1376
|
if (logsToAdd.length > 0) {
|
|
1261
1377
|
addStateLog(logsToAdd);
|
|
1262
1378
|
}
|
|
1263
|
-
|
|
1379
|
+
//console.timeEnd('logs');
|
|
1264
1380
|
signalUpdates.forEach(({ shadowMeta, displayValue }) => {
|
|
1265
1381
|
updateSignals(shadowMeta, displayValue);
|
|
1266
1382
|
});
|
|
1267
1383
|
|
|
1384
|
+
// console.time('updateComponents');
|
|
1268
1385
|
allComponentsToNotify.forEach((component) => {
|
|
1269
1386
|
component.forceUpdate();
|
|
1270
1387
|
});
|
|
1388
|
+
//console.timeEnd('updateComponents');
|
|
1271
1389
|
|
|
1272
1390
|
// --- Step 3: CLEANUP ---
|
|
1273
1391
|
// Clear the queue for the next batch of updates.
|
|
1274
1392
|
updateBatchQueue = [];
|
|
1275
1393
|
isFlushScheduled = false;
|
|
1276
1394
|
}
|
|
1277
|
-
|
|
1278
1395
|
function createEffectiveSetState<T>(
|
|
1279
1396
|
thisKey: string,
|
|
1280
|
-
syncApiRef: React.MutableRefObject<any>,
|
|
1281
1397
|
sessionId: string | undefined,
|
|
1282
1398
|
latestInitialOptionsRef: React.MutableRefObject<OptionsType<T> | null>
|
|
1283
1399
|
): EffectiveSetState<T> {
|
|
1284
|
-
|
|
1285
|
-
// It is now much simpler, delegating all work to the executeUpdate function.
|
|
1286
|
-
return (newStateOrFunction, path, updateObj, validationKey?) => {
|
|
1400
|
+
return (newStateOrFunction, path, updateObj) => {
|
|
1287
1401
|
executeUpdate(thisKey, path, newStateOrFunction, updateObj);
|
|
1288
1402
|
};
|
|
1289
1403
|
|
|
1290
|
-
// This inner function handles the logic for a single state update.
|
|
1291
1404
|
function executeUpdate(
|
|
1292
1405
|
stateKey: string,
|
|
1293
1406
|
path: string[],
|
|
1294
1407
|
payload: any,
|
|
1295
|
-
options: UpdateOptions
|
|
1408
|
+
options: UpdateOptions // Now includes itemId
|
|
1296
1409
|
) {
|
|
1297
|
-
// --- Step 1: Execute the core state change (Synchronous & Fast) ---
|
|
1298
|
-
// This part modifies the in-memory state representation immediately.
|
|
1299
1410
|
let result: any;
|
|
1300
1411
|
switch (options.updateType) {
|
|
1301
1412
|
case 'update':
|
|
1302
1413
|
result = handleUpdate(stateKey, path, payload);
|
|
1303
|
-
|
|
1304
1414
|
break;
|
|
1305
1415
|
case 'insert':
|
|
1306
|
-
result = handleInsert(
|
|
1307
|
-
|
|
1416
|
+
result = handleInsert(
|
|
1417
|
+
stateKey,
|
|
1418
|
+
path,
|
|
1419
|
+
payload,
|
|
1420
|
+
options.index,
|
|
1421
|
+
options.itemId
|
|
1422
|
+
);
|
|
1423
|
+
break;
|
|
1424
|
+
case 'insert_many':
|
|
1425
|
+
result = handleInsertMany(stateKey, path, payload);
|
|
1308
1426
|
break;
|
|
1309
1427
|
case 'cut':
|
|
1310
1428
|
result = handleCut(stateKey, path);
|
|
1311
|
-
|
|
1312
1429
|
break;
|
|
1313
1430
|
}
|
|
1314
|
-
|
|
1315
1431
|
result.stateKey = stateKey;
|
|
1316
1432
|
result.path = path;
|
|
1317
1433
|
updateBatchQueue.push(result);
|
|
@@ -1327,6 +1443,7 @@ function createEffectiveSetState<T>(
|
|
|
1327
1443
|
newValue: result.newValue ?? null,
|
|
1328
1444
|
itemId: result.itemId,
|
|
1329
1445
|
insertAfterId: result.insertAfterId,
|
|
1446
|
+
metaData: options.metaData,
|
|
1330
1447
|
};
|
|
1331
1448
|
|
|
1332
1449
|
updateBatchQueue.push(newUpdate);
|
|
@@ -1343,10 +1460,8 @@ function createEffectiveSetState<T>(
|
|
|
1343
1460
|
if (latestInitialOptionsRef.current?.middleware) {
|
|
1344
1461
|
latestInitialOptionsRef.current.middleware({ update: newUpdate });
|
|
1345
1462
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
syncApiRef.current.updateState({ operation: newUpdate });
|
|
1349
|
-
}
|
|
1463
|
+
runValidation(newUpdate, options.validationTrigger || 'programmatic');
|
|
1464
|
+
notifyUpdate(newUpdate);
|
|
1350
1465
|
}
|
|
1351
1466
|
}
|
|
1352
1467
|
|
|
@@ -1354,22 +1469,18 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1354
1469
|
stateObject: TStateObject,
|
|
1355
1470
|
{
|
|
1356
1471
|
stateKey,
|
|
1357
|
-
|
|
1358
1472
|
localStorage,
|
|
1359
1473
|
formElements,
|
|
1360
1474
|
reactiveDeps,
|
|
1361
1475
|
reactiveType,
|
|
1362
1476
|
componentId,
|
|
1363
1477
|
defaultState,
|
|
1364
|
-
syncUpdate,
|
|
1365
1478
|
dependencies,
|
|
1366
1479
|
serverState,
|
|
1367
|
-
__useSync,
|
|
1368
1480
|
}: {
|
|
1369
1481
|
stateKey?: string;
|
|
1370
1482
|
componentId?: string;
|
|
1371
1483
|
defaultState?: TStateObject;
|
|
1372
|
-
__useSync?: UseSyncType<TStateObject>;
|
|
1373
1484
|
syncOptions?: SyncOptionsType<any>;
|
|
1374
1485
|
} & OptionsType<TStateObject> = {}
|
|
1375
1486
|
) {
|
|
@@ -1384,16 +1495,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1384
1495
|
latestInitialOptionsRef.current = (getInitialOptions(thisKey as string) ??
|
|
1385
1496
|
null) as OptionsType<TStateObject> | null;
|
|
1386
1497
|
|
|
1387
|
-
useEffect(() => {
|
|
1388
|
-
if (syncUpdate && syncUpdate.stateKey === thisKey && syncUpdate.path?.[0]) {
|
|
1389
|
-
const syncKey = `${syncUpdate.stateKey}:${syncUpdate.path.join('.')}`;
|
|
1390
|
-
setSyncInfo(syncKey, {
|
|
1391
|
-
timeStamp: syncUpdate.timeStamp!,
|
|
1392
|
-
userId: syncUpdate.userId!,
|
|
1393
|
-
});
|
|
1394
|
-
}
|
|
1395
|
-
}, [syncUpdate]);
|
|
1396
|
-
|
|
1397
1498
|
const resolveInitialState = useCallback(
|
|
1398
1499
|
(
|
|
1399
1500
|
overrideOptions?: OptionsType<TStateObject>
|
|
@@ -1563,10 +1664,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1563
1664
|
const options = getInitialOptions(thisKey as string);
|
|
1564
1665
|
|
|
1565
1666
|
const features = {
|
|
1566
|
-
syncEnabled: !!cogsSyncFn && !!syncOpt,
|
|
1567
|
-
validationEnabled: !!(
|
|
1568
|
-
options?.validation?.zodSchemaV4 || options?.validation?.zodSchemaV3
|
|
1569
|
-
),
|
|
1570
1667
|
localStorageEnabled: !!options?.localStorage?.key,
|
|
1571
1668
|
};
|
|
1572
1669
|
|
|
@@ -1666,10 +1763,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1666
1763
|
};
|
|
1667
1764
|
}, []);
|
|
1668
1765
|
|
|
1669
|
-
const syncApiRef = useRef<SyncApi | null>(null);
|
|
1670
1766
|
const effectiveSetState = createEffectiveSetState(
|
|
1671
1767
|
thisKey,
|
|
1672
|
-
|
|
1768
|
+
|
|
1673
1769
|
sessionId,
|
|
1674
1770
|
latestInitialOptionsRef
|
|
1675
1771
|
);
|
|
@@ -1689,16 +1785,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1689
1785
|
return handler;
|
|
1690
1786
|
}, [thisKey, sessionId]);
|
|
1691
1787
|
|
|
1692
|
-
const cogsSyncFn = __useSync;
|
|
1693
|
-
const syncOpt = latestInitialOptionsRef.current?.syncOptions;
|
|
1694
|
-
console.log('syncOpt', syncOpt);
|
|
1695
|
-
if (cogsSyncFn) {
|
|
1696
|
-
syncApiRef.current = cogsSyncFn(
|
|
1697
|
-
updaterFinal as any,
|
|
1698
|
-
syncOpt ?? ({} as any)
|
|
1699
|
-
);
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
1788
|
return updaterFinal;
|
|
1703
1789
|
}
|
|
1704
1790
|
|
|
@@ -1845,7 +1931,6 @@ function createProxyHandler<T>(
|
|
|
1845
1931
|
sessionId?: string
|
|
1846
1932
|
): StateObject<T> {
|
|
1847
1933
|
const proxyCache = new Map<string, any>();
|
|
1848
|
-
let stateVersion = 0;
|
|
1849
1934
|
|
|
1850
1935
|
function rebuildStateShape({
|
|
1851
1936
|
path = [],
|
|
@@ -1859,6 +1944,7 @@ function createProxyHandler<T>(
|
|
|
1859
1944
|
const derivationSignature = meta
|
|
1860
1945
|
? JSON.stringify(meta.arrayViews || meta.transforms)
|
|
1861
1946
|
: '';
|
|
1947
|
+
|
|
1862
1948
|
const cacheKey =
|
|
1863
1949
|
path.join('.') + ':' + componentId + ':' + derivationSignature;
|
|
1864
1950
|
if (proxyCache.has(cacheKey)) {
|
|
@@ -1871,10 +1957,17 @@ function createProxyHandler<T>(
|
|
|
1871
1957
|
|
|
1872
1958
|
const handler = {
|
|
1873
1959
|
get(target: any, prop: string) {
|
|
1960
|
+
if (typeof prop !== 'string') {
|
|
1961
|
+
// This is a Symbol. Let the default "get" behavior happen.
|
|
1962
|
+
// This allows internal JS operations and dev tools to work correctly
|
|
1963
|
+
// without interfering with your state logic.
|
|
1964
|
+
return Reflect.get(target, prop);
|
|
1965
|
+
}
|
|
1874
1966
|
if (path.length === 0 && prop in rootLevelMethods) {
|
|
1875
1967
|
return rootLevelMethods[prop as keyof typeof rootLevelMethods];
|
|
1876
1968
|
}
|
|
1877
|
-
|
|
1969
|
+
|
|
1970
|
+
if (typeof prop === 'string' && !prop.startsWith('$')) {
|
|
1878
1971
|
const nextPath = [...path, prop];
|
|
1879
1972
|
return rebuildStateShape({
|
|
1880
1973
|
path: nextPath,
|
|
@@ -2800,8 +2893,6 @@ function createProxyHandler<T>(
|
|
|
2800
2893
|
|
|
2801
2894
|
if (!Array.isArray(currentState)) return [];
|
|
2802
2895
|
|
|
2803
|
-
stateVersion++;
|
|
2804
|
-
|
|
2805
2896
|
return rebuildStateShape({
|
|
2806
2897
|
path: [...path, '[*]', fieldName],
|
|
2807
2898
|
componentId: componentId!,
|
|
@@ -2863,7 +2954,18 @@ function createProxyHandler<T>(
|
|
|
2863
2954
|
payload: InsertParams<InferArrayElement<T>>,
|
|
2864
2955
|
index?: number
|
|
2865
2956
|
) => {
|
|
2866
|
-
effectiveSetState(payload as any, path, {
|
|
2957
|
+
effectiveSetState(payload as any, path, {
|
|
2958
|
+
updateType: 'insert',
|
|
2959
|
+
index,
|
|
2960
|
+
});
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
if (prop === '$insertMany') {
|
|
2964
|
+
return (payload: InferArrayElement<T>[]) => {
|
|
2965
|
+
// Call the one true path for all state changes.
|
|
2966
|
+
effectiveSetState(payload as any, path, {
|
|
2967
|
+
updateType: 'insert_many',
|
|
2968
|
+
});
|
|
2867
2969
|
};
|
|
2868
2970
|
}
|
|
2869
2971
|
if (prop === '$uniqueInsert') {
|
|
@@ -2911,6 +3013,7 @@ function createProxyHandler<T>(
|
|
|
2911
3013
|
if (prop === '$cut') {
|
|
2912
3014
|
return (index?: number, options?: { waitForSync?: boolean }) => {
|
|
2913
3015
|
const shadowMeta = getShadowMetadata(stateKey, path);
|
|
3016
|
+
console.log('shadowMeta ->>>>>>>>>>>>>>>>', shadowMeta);
|
|
2914
3017
|
if (!shadowMeta?.arrayKeys || shadowMeta.arrayKeys.length === 0)
|
|
2915
3018
|
return;
|
|
2916
3019
|
|
|
@@ -2921,8 +3024,11 @@ function createProxyHandler<T>(
|
|
|
2921
3024
|
? index
|
|
2922
3025
|
: shadowMeta.arrayKeys.length - 1;
|
|
2923
3026
|
|
|
3027
|
+
console.log('indexToCut ->>>>>>>>>>>>>>>>', indexToCut);
|
|
3028
|
+
|
|
2924
3029
|
const idToCut = shadowMeta.arrayKeys[indexToCut];
|
|
2925
3030
|
if (!idToCut) return;
|
|
3031
|
+
console.log('idToCut ->>>>>>>>>>>>>>>>', idToCut);
|
|
2926
3032
|
|
|
2927
3033
|
effectiveSetState(null, [...path, idToCut], {
|
|
2928
3034
|
updateType: 'cut',
|
|
@@ -3024,11 +3130,7 @@ function createProxyHandler<T>(
|
|
|
3024
3130
|
});
|
|
3025
3131
|
}
|
|
3026
3132
|
|
|
3027
|
-
return
|
|
3028
|
-
path: [...path, `not_found_${uuidv4()}`],
|
|
3029
|
-
componentId: componentId!,
|
|
3030
|
-
meta,
|
|
3031
|
-
});
|
|
3133
|
+
return null;
|
|
3032
3134
|
};
|
|
3033
3135
|
}
|
|
3034
3136
|
if (prop === '$cutThis') {
|
|
@@ -3058,10 +3160,59 @@ function createProxyHandler<T>(
|
|
|
3058
3160
|
});
|
|
3059
3161
|
}
|
|
3060
3162
|
|
|
3163
|
+
// if (prop === '$formInput') {
|
|
3164
|
+
// const _getFormElement = (path: string[]): HTMLElement | null => {
|
|
3165
|
+
// const metadata = getShadowMetadata(stateKey, path);
|
|
3166
|
+
// if (metadata?.formRef?.current) {
|
|
3167
|
+
// return metadata.formRef.current;
|
|
3168
|
+
// }
|
|
3169
|
+
// // This warning is helpful for debugging if a ref is missing.
|
|
3170
|
+
// console.warn(
|
|
3171
|
+
// `Form element ref not found for stateKey "${stateKey}" at path "${path.join('.')}"`
|
|
3172
|
+
// );
|
|
3173
|
+
// return null;
|
|
3174
|
+
// };
|
|
3175
|
+
// return {
|
|
3176
|
+
// setDisabled: (isDisabled: boolean) => {
|
|
3177
|
+
// const element = _getFormElement(path) as HTMLInputElement | null;
|
|
3178
|
+
// if (element) {
|
|
3179
|
+
// element.disabled = isDisabled;
|
|
3180
|
+
// }
|
|
3181
|
+
// },
|
|
3182
|
+
// focus: () => {
|
|
3183
|
+
// const element = _getFormElement(path);
|
|
3184
|
+
// element?.focus();
|
|
3185
|
+
// },
|
|
3186
|
+
// blur: () => {
|
|
3187
|
+
// const element = _getFormElement(path);
|
|
3188
|
+
// element?.blur();
|
|
3189
|
+
// },
|
|
3190
|
+
// scrollIntoView: (options?: ScrollIntoViewOptions) => {
|
|
3191
|
+
// const element = _getFormElement(path);
|
|
3192
|
+
// element?.scrollIntoView(
|
|
3193
|
+
// options ?? { behavior: 'smooth', block: 'center' }
|
|
3194
|
+
// );
|
|
3195
|
+
// },
|
|
3196
|
+
// click: () => {
|
|
3197
|
+
// const element = _getFormElement(path);
|
|
3198
|
+
// element?.click();
|
|
3199
|
+
// },
|
|
3200
|
+
// selectText: () => {
|
|
3201
|
+
// const element = _getFormElement(path) as
|
|
3202
|
+
// | HTMLInputElement
|
|
3203
|
+
// | HTMLTextAreaElement
|
|
3204
|
+
// | null;
|
|
3205
|
+
// if (element && typeof element.select === 'function') {
|
|
3206
|
+
// element.select();
|
|
3207
|
+
// }
|
|
3208
|
+
// },
|
|
3209
|
+
// };
|
|
3210
|
+
// }
|
|
3061
3211
|
if (prop === '$$get') {
|
|
3062
3212
|
return () =>
|
|
3063
3213
|
$cogsSignal({ _stateKey: stateKey, _path: path, _meta: meta });
|
|
3064
3214
|
}
|
|
3215
|
+
|
|
3065
3216
|
if (prop === '$lastSynced') {
|
|
3066
3217
|
const syncKey = `${stateKey}:${path.join('.')}`;
|
|
3067
3218
|
return getSyncInfo(syncKey);
|
|
@@ -3133,6 +3284,38 @@ function createProxyHandler<T>(
|
|
|
3133
3284
|
return componentId;
|
|
3134
3285
|
}
|
|
3135
3286
|
if (path.length == 0) {
|
|
3287
|
+
if (prop === '$setOptions') {
|
|
3288
|
+
return (options: OptionsType<T>) => {
|
|
3289
|
+
setOptions({ stateKey, options, initialOptionsPart: {} });
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
if (prop === '$_applyUpdate') {
|
|
3294
|
+
return (
|
|
3295
|
+
value: any,
|
|
3296
|
+
path: string[],
|
|
3297
|
+
updateType: 'update' | 'insert' | 'cut' = 'update'
|
|
3298
|
+
) => {
|
|
3299
|
+
effectiveSetState(value, path, { updateType });
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
if (prop === '$_getEffectiveSetState') {
|
|
3304
|
+
return effectiveSetState;
|
|
3305
|
+
}
|
|
3306
|
+
if (prop === '$getPluginMetaData') {
|
|
3307
|
+
return (pluginName: string) =>
|
|
3308
|
+
getPluginMetaDataMap(stateKey, path)?.get(pluginName);
|
|
3309
|
+
}
|
|
3310
|
+
if (prop === '$addPluginMetaData') {
|
|
3311
|
+
console.log('$addPluginMetaDat');
|
|
3312
|
+
return (pluginName: string, data: Record<string, any>) =>
|
|
3313
|
+
setPluginMetaData(stateKey, path, pluginName, data);
|
|
3314
|
+
}
|
|
3315
|
+
if (prop === '$removePluginMetaData') {
|
|
3316
|
+
return (pluginName: string) =>
|
|
3317
|
+
removePluginMetaData(stateKey, path, pluginName);
|
|
3318
|
+
}
|
|
3136
3319
|
if (prop === '$addZodValidation') {
|
|
3137
3320
|
return (
|
|
3138
3321
|
zodErrors: any[],
|
|
@@ -3185,46 +3368,67 @@ function createProxyHandler<T>(
|
|
|
3185
3368
|
}
|
|
3186
3369
|
|
|
3187
3370
|
if (prop === '$applyOperation') {
|
|
3188
|
-
return (
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3371
|
+
return (
|
|
3372
|
+
operation: UpdateTypeDetail & {
|
|
3373
|
+
validation?: any[];
|
|
3374
|
+
version?: string;
|
|
3375
|
+
},
|
|
3376
|
+
metaData?: Record<string, any>
|
|
3377
|
+
) => {
|
|
3378
|
+
// const validationErrorsFromServer = operation.validation || [];
|
|
3379
|
+
|
|
3380
|
+
// if (!operation || !operation.path) {
|
|
3381
|
+
// console.error(
|
|
3382
|
+
// 'Invalid operation received by $applyOperation:',
|
|
3383
|
+
// operation
|
|
3384
|
+
// );
|
|
3385
|
+
// return;
|
|
3386
|
+
// }
|
|
3387
|
+
|
|
3388
|
+
// const newErrors: ValidationError[] =
|
|
3389
|
+
// validationErrorsFromServer.map((err) => ({
|
|
3390
|
+
// source: 'sync_engine',
|
|
3391
|
+
// message: err.message,
|
|
3392
|
+
// severity: 'warning',
|
|
3393
|
+
// code: err.code,
|
|
3394
|
+
// }));
|
|
3395
|
+
// console.log('newErrors', newErrors);
|
|
3396
|
+
// getGlobalStore
|
|
3397
|
+
// .getState()
|
|
3398
|
+
// .setShadowMetadata(stateKey, operation.path, {
|
|
3399
|
+
// validation: {
|
|
3400
|
+
// status: newErrors.length > 0 ? 'INVALID' : 'VALID',
|
|
3401
|
+
// errors: newErrors,
|
|
3402
|
+
// lastValidated: Date.now(),
|
|
3403
|
+
// },
|
|
3404
|
+
// });
|
|
3405
|
+
console.log(
|
|
3406
|
+
'getGlobalStore',
|
|
3202
3407
|
getGlobalStore
|
|
3203
3408
|
.getState()
|
|
3204
|
-
.getShadowMetadata(stateKey,
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3409
|
+
.getShadowMetadata(stateKey, operation.path)
|
|
3410
|
+
);
|
|
3411
|
+
let index: number | undefined;
|
|
3412
|
+
if (
|
|
3413
|
+
operation.insertAfterId &&
|
|
3414
|
+
operation.updateType === 'insert'
|
|
3415
|
+
) {
|
|
3416
|
+
const shadowMeta = getShadowMetadata(stateKey, operation.path);
|
|
3417
|
+
if (shadowMeta?.arrayKeys) {
|
|
3418
|
+
const afterIndex = shadowMeta.arrayKeys.indexOf(
|
|
3419
|
+
operation.insertAfterId
|
|
3420
|
+
);
|
|
3421
|
+
if (afterIndex !== -1) {
|
|
3422
|
+
index = afterIndex + 1; // Insert after the found item
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3213
3426
|
|
|
3214
|
-
|
|
3215
|
-
getGlobalStore
|
|
3216
|
-
.getState()
|
|
3217
|
-
.setShadowMetadata(stateKey, updatePath, {
|
|
3218
|
-
...currentMeta,
|
|
3219
|
-
validation: {
|
|
3220
|
-
status: newErrors.length > 0 ? 'INVALID' : 'VALID',
|
|
3221
|
-
errors: newErrors,
|
|
3222
|
-
lastValidated: Date.now(),
|
|
3223
|
-
},
|
|
3224
|
-
});
|
|
3225
|
-
effectiveSetState(operation.newValue, updatePath, {
|
|
3427
|
+
effectiveSetState(operation.newValue, operation.path, {
|
|
3226
3428
|
updateType: operation.updateType,
|
|
3227
|
-
|
|
3429
|
+
itemId: operation.itemId,
|
|
3430
|
+
index, // Pass the calculated index
|
|
3431
|
+
metaData,
|
|
3228
3432
|
});
|
|
3229
3433
|
};
|
|
3230
3434
|
}
|
|
@@ -3322,14 +3526,8 @@ function createProxyHandler<T>(
|
|
|
3322
3526
|
|
|
3323
3527
|
if (prop === '$getComponents')
|
|
3324
3528
|
return () => getShadowMetadata(stateKey, [])?.components;
|
|
3325
|
-
if (prop === '$getAllFormRefs')
|
|
3326
|
-
return () =>
|
|
3327
|
-
formRefStore.getState().getFormRefsByStateKey(stateKey);
|
|
3328
|
-
}
|
|
3329
|
-
if (prop === '$getFormRef') {
|
|
3330
|
-
return () =>
|
|
3331
|
-
formRefStore.getState().getFormRef(stateKey + '.' + path.join('.'));
|
|
3332
3529
|
}
|
|
3530
|
+
|
|
3333
3531
|
if (prop === '$validationWrapper') {
|
|
3334
3532
|
return ({
|
|
3335
3533
|
children,
|
|
@@ -3349,6 +3547,7 @@ function createProxyHandler<T>(
|
|
|
3349
3547
|
</ValidationWrapper>
|
|
3350
3548
|
);
|
|
3351
3549
|
}
|
|
3550
|
+
|
|
3352
3551
|
if (prop === '$_stateKey') return stateKey;
|
|
3353
3552
|
if (prop === '$_path') return path;
|
|
3354
3553
|
if (prop === '$update') {
|
|
@@ -3469,9 +3668,11 @@ function createProxyHandler<T>(
|
|
|
3469
3668
|
|
|
3470
3669
|
return revertState;
|
|
3471
3670
|
},
|
|
3671
|
+
$initializeAndMergeShadowState: (newState: T) => {
|
|
3672
|
+
initializeAndMergeShadowState(stateKey, newState);
|
|
3673
|
+
notifyComponents(stateKey);
|
|
3674
|
+
},
|
|
3472
3675
|
$updateInitialState: (newState: T) => {
|
|
3473
|
-
stateVersion++;
|
|
3474
|
-
|
|
3475
3676
|
const newUpdaterState = createProxyHandler(
|
|
3476
3677
|
stateKey,
|
|
3477
3678
|
effectiveSetState,
|