cogsbox-state 0.5.472 → 0.5.474
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/README.md +48 -18
- package/dist/CogsState.d.ts +98 -82
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1030 -960
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.jsx +299 -219
- 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 +122 -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 +81 -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 +1323 -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 +657 -457
- package/src/Components.tsx +291 -194
- package/src/PluginRunner.tsx +203 -0
- package/src/index.ts +2 -0
- package/src/pluginStore.ts +176 -0
- package/src/plugins.ts +544 -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,29 @@ 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
|
+
pluginStore
|
|
821
|
+
.getState()
|
|
822
|
+
.stateHandlers.set(stateKey as string, updater as any);
|
|
823
|
+
|
|
824
|
+
return () => {
|
|
825
|
+
pluginStore.getState().stateHandlers.delete(stateKey as string);
|
|
826
|
+
};
|
|
827
|
+
}, [stateKey, updater]);
|
|
828
|
+
|
|
755
829
|
return updater;
|
|
756
830
|
};
|
|
757
831
|
|
|
758
|
-
function
|
|
832
|
+
function setCogsOptionsByKey<StateKey extends StateKeys>(
|
|
759
833
|
stateKey: StateKey,
|
|
760
834
|
options: OptionsType<(typeof statePart)[StateKey]>
|
|
761
835
|
) {
|
|
@@ -768,109 +842,53 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
768
842
|
notifyComponents(stateKey as string);
|
|
769
843
|
}
|
|
770
844
|
|
|
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;
|
|
845
|
+
function setCogsFormElements(
|
|
846
|
+
formElements: FormsElementsType<State, TPlugins>
|
|
847
|
+
) {
|
|
848
|
+
// Get the current list of registered plugins from the store.
|
|
849
|
+
const currentPlugins = pluginStore.getState().registeredPlugins;
|
|
850
|
+
|
|
851
|
+
// Create a new array by mapping over the current plugins.
|
|
852
|
+
// This is crucial for immutability and ensuring Zustand detects the change.
|
|
853
|
+
const updatedPlugins = currentPlugins.map((plugin) => {
|
|
854
|
+
// Check if the formElements object has a wrapper for this specific plugin by name.
|
|
855
|
+
if (formElements.hasOwnProperty(plugin.name)) {
|
|
856
|
+
// If it does, return a *new* plugin object.
|
|
857
|
+
// Spread the existing plugin properties and add/overwrite the formWrapper.
|
|
858
|
+
return {
|
|
859
|
+
...plugin,
|
|
860
|
+
formWrapper: formElements[plugin.name as keyof typeof formElements],
|
|
817
861
|
};
|
|
818
|
-
[key: string]: any;
|
|
819
862
|
}
|
|
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);
|
|
863
|
+
// If there's no new wrapper for this plugin, return the original object.
|
|
864
|
+
return plugin;
|
|
865
|
+
});
|
|
859
866
|
|
|
860
|
-
//
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
867
|
+
// Use the store's dedicated setter function to update the registered plugins list.
|
|
868
|
+
// This will trigger a state update that components listening to the store will react to.
|
|
869
|
+
pluginStore.getState().setRegisteredPlugins(updatedPlugins as any);
|
|
870
|
+
|
|
871
|
+
// For good measure and consistency, we should still update the formElements
|
|
872
|
+
// in the initial options, in case any other part of the system relies on it.
|
|
873
|
+
const allStateKeys = Object.keys(statePart);
|
|
874
|
+
allStateKeys.forEach((key) => {
|
|
875
|
+
const existingOptions = getInitialOptions(key) || {};
|
|
876
|
+
const finalOptions = {
|
|
877
|
+
...existingOptions,
|
|
878
|
+
formElements: {
|
|
879
|
+
...(existingOptions.formElements || {}),
|
|
880
|
+
...formElements,
|
|
881
|
+
},
|
|
882
|
+
};
|
|
883
|
+
setInitialStateOptions(key, finalOptions);
|
|
884
|
+
});
|
|
864
885
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
__syncSchemas: schemas,
|
|
872
|
-
}) as any;
|
|
873
|
-
}
|
|
886
|
+
return {
|
|
887
|
+
useCogsState,
|
|
888
|
+
setCogsOptionsByKey,
|
|
889
|
+
setCogsFormElements,
|
|
890
|
+
};
|
|
891
|
+
};
|
|
874
892
|
|
|
875
893
|
const saveToLocalStorage = <T,>(
|
|
876
894
|
state: T,
|
|
@@ -1042,35 +1060,14 @@ let isFlushScheduled = false;
|
|
|
1042
1060
|
function scheduleFlush() {
|
|
1043
1061
|
if (!isFlushScheduled) {
|
|
1044
1062
|
isFlushScheduled = true;
|
|
1045
|
-
|
|
1063
|
+
console.log('Scheduling flush');
|
|
1064
|
+
queueMicrotask(() => {
|
|
1065
|
+
console.log('Actually flushing');
|
|
1066
|
+
flushQueue();
|
|
1067
|
+
});
|
|
1046
1068
|
}
|
|
1047
1069
|
}
|
|
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
1070
|
|
|
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
1071
|
// 2. Update signals
|
|
1075
1072
|
function updateSignals(shadowMeta: any, displayValue: any) {
|
|
1076
1073
|
if (!shadowMeta?.signals?.length) return;
|
|
@@ -1114,72 +1111,179 @@ function getComponentNotifications(
|
|
|
1114
1111
|
|
|
1115
1112
|
const componentsToNotify = new Set<any>();
|
|
1116
1113
|
|
|
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);
|
|
1114
|
+
// --- PASS 1: Notify specific subscribers based on update type ---
|
|
1115
|
+
|
|
1116
|
+
if (result.type === 'update') {
|
|
1117
|
+
// --- Bubble-up Notification ---
|
|
1118
|
+
// An update to `user.address.street` notifies listeners of `street`, `address`, and `user`.
|
|
1119
|
+
let currentPath = [...path];
|
|
1120
|
+
while (true) {
|
|
1121
|
+
const pathMeta = getShadowMetadata(stateKey, currentPath);
|
|
1122
|
+
|
|
1123
|
+
if (pathMeta?.pathComponents) {
|
|
1124
|
+
pathMeta.pathComponents.forEach((componentId: string) => {
|
|
1125
|
+
const component = rootMeta.components?.get(componentId);
|
|
1126
|
+
// NEW: Add component to the set instead of calling forceUpdate()
|
|
1127
|
+
if (component) {
|
|
1128
|
+
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1129
|
+
? component.reactiveType
|
|
1130
|
+
: [component.reactiveType || 'component'];
|
|
1131
|
+
if (!reactiveTypes.includes('none')) {
|
|
1132
|
+
componentsToNotify.add(component);
|
|
1133
|
+
}
|
|
1138
1134
|
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
if (currentPath.length === 0) break;
|
|
1139
|
+
currentPath.pop(); // Go up one level
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// --- Deep Object Change Notification ---
|
|
1143
|
+
// If the new value is an object, notify components subscribed to sub-paths that changed.
|
|
1144
|
+
if (
|
|
1145
|
+
result.newValue &&
|
|
1146
|
+
typeof result.newValue === 'object' &&
|
|
1147
|
+
!isArray(result.newValue)
|
|
1148
|
+
) {
|
|
1149
|
+
const changedSubPaths = getDifferences(result.newValue, result.oldValue);
|
|
1150
|
+
|
|
1151
|
+
changedSubPaths.forEach((subPathString: string) => {
|
|
1152
|
+
const subPath = subPathString.split('.');
|
|
1153
|
+
const fullSubPath = [...path, ...subPath];
|
|
1154
|
+
const subPathMeta = getShadowMetadata(stateKey, fullSubPath);
|
|
1155
|
+
|
|
1156
|
+
if (subPathMeta?.pathComponents) {
|
|
1157
|
+
subPathMeta.pathComponents.forEach((componentId: string) => {
|
|
1158
|
+
const component = rootMeta.components?.get(componentId);
|
|
1159
|
+
// NEW: Add component to the set
|
|
1160
|
+
if (component) {
|
|
1161
|
+
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1162
|
+
? component.reactiveType
|
|
1163
|
+
: [component.reactiveType || 'component'];
|
|
1164
|
+
if (!reactiveTypes.includes('none')) {
|
|
1165
|
+
componentsToNotify.add(component);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1139
1169
|
}
|
|
1140
1170
|
});
|
|
1141
1171
|
}
|
|
1172
|
+
} else if (
|
|
1173
|
+
result.type === 'insert' ||
|
|
1174
|
+
result.type === 'cut' ||
|
|
1175
|
+
result.type === 'insert_many'
|
|
1176
|
+
) {
|
|
1177
|
+
// This path is the array itself (e.g., ['pets'])
|
|
1178
|
+
const parentArrayPath = result.type === 'insert' ? path : path.slice(0, -1);
|
|
1179
|
+
|
|
1180
|
+
// --- FIX: ADD BUBBLE-UP LOGIC HERE ---
|
|
1181
|
+
// Start from the array's path and go up to the root
|
|
1182
|
+
let currentPath = [...parentArrayPath];
|
|
1183
|
+
while (true) {
|
|
1184
|
+
const pathMeta = getShadowMetadata(stateKey, currentPath);
|
|
1185
|
+
|
|
1186
|
+
if (pathMeta?.pathComponents) {
|
|
1187
|
+
pathMeta.pathComponents.forEach((componentId: string) => {
|
|
1188
|
+
const component = rootMeta.components?.get(componentId);
|
|
1189
|
+
if (component) {
|
|
1190
|
+
componentsToNotify.add(component);
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1142
1194
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1195
|
+
if (currentPath.length === 0) break;
|
|
1196
|
+
currentPath.pop(); // Go up one level
|
|
1197
|
+
}
|
|
1198
|
+
// --- END FIX ---
|
|
1145
1199
|
}
|
|
1146
1200
|
|
|
1147
1201
|
// --- PASS 2: Handle 'all' and 'deps' reactivity types ---
|
|
1148
1202
|
// 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
|
-
});
|
|
1203
|
+
// rootMeta.components.forEach((component, componentId) => {
|
|
1204
|
+
// // If we've already added this component, skip it.
|
|
1205
|
+
// if (componentsToNotify.has(component)) {
|
|
1206
|
+
// return;
|
|
1207
|
+
// }
|
|
1208
|
+
|
|
1209
|
+
// const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1210
|
+
// ? component.reactiveType
|
|
1211
|
+
// : [component.reactiveType || 'component'];
|
|
1212
|
+
|
|
1213
|
+
// if (reactiveTypes.includes('all')) {
|
|
1214
|
+
// componentsToNotify.add(component);
|
|
1215
|
+
// } else if (reactiveTypes.includes('deps') && component.depsFunction) {
|
|
1216
|
+
// const currentState = getShadowValue(stateKey, []);
|
|
1217
|
+
// const newDeps = component.depsFunction(currentState);
|
|
1218
|
+
|
|
1219
|
+
// if (
|
|
1220
|
+
// newDeps === true ||
|
|
1221
|
+
// (Array.isArray(newDeps) && !isDeepEqual(component.prevDeps, newDeps))
|
|
1222
|
+
// ) {
|
|
1223
|
+
// component.prevDeps = newDeps as any; // Update the dependencies for the next check
|
|
1224
|
+
// componentsToNotify.add(component);
|
|
1225
|
+
// }
|
|
1226
|
+
// }
|
|
1227
|
+
// });
|
|
1174
1228
|
|
|
1175
1229
|
return componentsToNotify;
|
|
1176
1230
|
}
|
|
1177
1231
|
|
|
1232
|
+
function handleUpdate(
|
|
1233
|
+
stateKey: string,
|
|
1234
|
+
path: string[],
|
|
1235
|
+
payload: any
|
|
1236
|
+
): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
|
|
1237
|
+
// ✅ FIX: Get the old value before the update.
|
|
1238
|
+
const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
|
|
1239
|
+
|
|
1240
|
+
const newValue = isFunction(payload) ? payload(oldValue) : payload;
|
|
1241
|
+
|
|
1242
|
+
// ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
|
|
1243
|
+
// The manual loop has been removed.
|
|
1244
|
+
updateShadowAtPath(stateKey, path, newValue);
|
|
1245
|
+
|
|
1246
|
+
markAsDirty(stateKey, path, { bubble: true });
|
|
1247
|
+
|
|
1248
|
+
// Return the metadata of the node *after* the update.
|
|
1249
|
+
const newShadowMeta = getShadowMetadata(stateKey, path);
|
|
1250
|
+
|
|
1251
|
+
return {
|
|
1252
|
+
type: 'update',
|
|
1253
|
+
oldValue: oldValue,
|
|
1254
|
+
newValue,
|
|
1255
|
+
shadowMeta: newShadowMeta,
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
function handleInsertMany(
|
|
1259
|
+
stateKey: string,
|
|
1260
|
+
path: string[],
|
|
1261
|
+
payload: any[]
|
|
1262
|
+
): {
|
|
1263
|
+
type: 'insert_many';
|
|
1264
|
+
count: number;
|
|
1265
|
+
shadowMeta: any;
|
|
1266
|
+
path: string[];
|
|
1267
|
+
} {
|
|
1268
|
+
// Use the existing, optimized global store function to perform the state update
|
|
1269
|
+
insertManyShadowArrayElements(stateKey, path, payload);
|
|
1270
|
+
|
|
1271
|
+
markAsDirty(stateKey, path, { bubble: true });
|
|
1272
|
+
const updatedMeta = getShadowMetadata(stateKey, path);
|
|
1273
|
+
|
|
1274
|
+
return {
|
|
1275
|
+
type: 'insert_many',
|
|
1276
|
+
count: payload.length,
|
|
1277
|
+
shadowMeta: updatedMeta,
|
|
1278
|
+
path: path,
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1178
1281
|
function handleInsert(
|
|
1179
1282
|
stateKey: string,
|
|
1180
1283
|
path: string[],
|
|
1181
1284
|
payload: any,
|
|
1182
|
-
index?: number
|
|
1285
|
+
index?: number,
|
|
1286
|
+
itemId?: string
|
|
1183
1287
|
): {
|
|
1184
1288
|
type: 'insert';
|
|
1185
1289
|
newValue: any;
|
|
@@ -1189,19 +1293,28 @@ function handleInsert(
|
|
|
1189
1293
|
insertAfterId?: string;
|
|
1190
1294
|
} {
|
|
1191
1295
|
let newValue;
|
|
1296
|
+
|
|
1192
1297
|
if (isFunction(payload)) {
|
|
1193
1298
|
const { value: currentValue } = getScopedData(stateKey, path);
|
|
1194
|
-
newValue = payload({ state: currentValue
|
|
1299
|
+
newValue = payload({ state: currentValue });
|
|
1195
1300
|
} else {
|
|
1196
1301
|
newValue = payload;
|
|
1197
1302
|
}
|
|
1303
|
+
//console.time('insertShadowArrayElement');
|
|
1304
|
+
// Pass itemId to insertShadowArrayElement
|
|
1305
|
+
const actualItemId = insertShadowArrayElement(
|
|
1306
|
+
stateKey,
|
|
1307
|
+
path,
|
|
1308
|
+
newValue,
|
|
1309
|
+
index,
|
|
1310
|
+
itemId
|
|
1311
|
+
);
|
|
1312
|
+
//console.timeEnd('insertShadowArrayElement');
|
|
1198
1313
|
|
|
1199
|
-
const itemId = insertShadowArrayElement(stateKey, path, newValue, index);
|
|
1200
1314
|
markAsDirty(stateKey, path, { bubble: true });
|
|
1201
1315
|
|
|
1202
1316
|
const updatedMeta = getShadowMetadata(stateKey, path);
|
|
1203
1317
|
|
|
1204
|
-
// Find the ID that comes before this insertion point
|
|
1205
1318
|
let insertAfterId: string | undefined;
|
|
1206
1319
|
if (updatedMeta?.arrayKeys && index !== undefined && index > 0) {
|
|
1207
1320
|
insertAfterId = updatedMeta.arrayKeys[index - 1];
|
|
@@ -1211,11 +1324,12 @@ function handleInsert(
|
|
|
1211
1324
|
type: 'insert',
|
|
1212
1325
|
newValue,
|
|
1213
1326
|
shadowMeta: updatedMeta,
|
|
1214
|
-
path: path,
|
|
1215
|
-
itemId:
|
|
1327
|
+
path: path,
|
|
1328
|
+
itemId: actualItemId,
|
|
1216
1329
|
insertAfterId: insertAfterId,
|
|
1217
1330
|
};
|
|
1218
1331
|
}
|
|
1332
|
+
|
|
1219
1333
|
function handleCut(
|
|
1220
1334
|
stateKey: string,
|
|
1221
1335
|
path: string[]
|
|
@@ -1245,7 +1359,7 @@ function flushQueue() {
|
|
|
1245
1359
|
if (result.shadowMeta?.signals?.length > 0) {
|
|
1246
1360
|
signalUpdates.push({ shadowMeta: result.shadowMeta, displayValue });
|
|
1247
1361
|
}
|
|
1248
|
-
|
|
1362
|
+
// console.time('getComponentNotifications');
|
|
1249
1363
|
const componentNotifications = getComponentNotifications(
|
|
1250
1364
|
result.stateKey,
|
|
1251
1365
|
result.path,
|
|
@@ -1255,63 +1369,64 @@ function flushQueue() {
|
|
|
1255
1369
|
componentNotifications.forEach((component) => {
|
|
1256
1370
|
allComponentsToNotify.add(component);
|
|
1257
1371
|
});
|
|
1372
|
+
// console.timeEnd('getComponentNotifications');
|
|
1258
1373
|
}
|
|
1259
|
-
|
|
1374
|
+
//console.time('logs');
|
|
1260
1375
|
if (logsToAdd.length > 0) {
|
|
1261
1376
|
addStateLog(logsToAdd);
|
|
1262
1377
|
}
|
|
1263
|
-
|
|
1378
|
+
//console.timeEnd('logs');
|
|
1264
1379
|
signalUpdates.forEach(({ shadowMeta, displayValue }) => {
|
|
1265
1380
|
updateSignals(shadowMeta, displayValue);
|
|
1266
1381
|
});
|
|
1267
1382
|
|
|
1383
|
+
// console.time('updateComponents');
|
|
1268
1384
|
allComponentsToNotify.forEach((component) => {
|
|
1269
1385
|
component.forceUpdate();
|
|
1270
1386
|
});
|
|
1387
|
+
//console.timeEnd('updateComponents');
|
|
1271
1388
|
|
|
1272
1389
|
// --- Step 3: CLEANUP ---
|
|
1273
1390
|
// Clear the queue for the next batch of updates.
|
|
1274
1391
|
updateBatchQueue = [];
|
|
1275
1392
|
isFlushScheduled = false;
|
|
1276
1393
|
}
|
|
1277
|
-
|
|
1278
1394
|
function createEffectiveSetState<T>(
|
|
1279
1395
|
thisKey: string,
|
|
1280
|
-
syncApiRef: React.MutableRefObject<any>,
|
|
1281
1396
|
sessionId: string | undefined,
|
|
1282
1397
|
latestInitialOptionsRef: React.MutableRefObject<OptionsType<T> | null>
|
|
1283
1398
|
): EffectiveSetState<T> {
|
|
1284
|
-
|
|
1285
|
-
// It is now much simpler, delegating all work to the executeUpdate function.
|
|
1286
|
-
return (newStateOrFunction, path, updateObj, validationKey?) => {
|
|
1399
|
+
return (newStateOrFunction, path, updateObj) => {
|
|
1287
1400
|
executeUpdate(thisKey, path, newStateOrFunction, updateObj);
|
|
1288
1401
|
};
|
|
1289
1402
|
|
|
1290
|
-
// This inner function handles the logic for a single state update.
|
|
1291
1403
|
function executeUpdate(
|
|
1292
1404
|
stateKey: string,
|
|
1293
1405
|
path: string[],
|
|
1294
1406
|
payload: any,
|
|
1295
|
-
options: UpdateOptions
|
|
1407
|
+
options: UpdateOptions // Now includes itemId
|
|
1296
1408
|
) {
|
|
1297
|
-
// --- Step 1: Execute the core state change (Synchronous & Fast) ---
|
|
1298
|
-
// This part modifies the in-memory state representation immediately.
|
|
1299
1409
|
let result: any;
|
|
1300
1410
|
switch (options.updateType) {
|
|
1301
1411
|
case 'update':
|
|
1302
1412
|
result = handleUpdate(stateKey, path, payload);
|
|
1303
|
-
|
|
1304
1413
|
break;
|
|
1305
1414
|
case 'insert':
|
|
1306
|
-
result = handleInsert(
|
|
1307
|
-
|
|
1415
|
+
result = handleInsert(
|
|
1416
|
+
stateKey,
|
|
1417
|
+
path,
|
|
1418
|
+
payload,
|
|
1419
|
+
options.index,
|
|
1420
|
+
options.itemId
|
|
1421
|
+
);
|
|
1422
|
+
break;
|
|
1423
|
+
case 'insert_many':
|
|
1424
|
+
result = handleInsertMany(stateKey, path, payload);
|
|
1308
1425
|
break;
|
|
1309
1426
|
case 'cut':
|
|
1310
1427
|
result = handleCut(stateKey, path);
|
|
1311
|
-
|
|
1312
1428
|
break;
|
|
1313
1429
|
}
|
|
1314
|
-
|
|
1315
1430
|
result.stateKey = stateKey;
|
|
1316
1431
|
result.path = path;
|
|
1317
1432
|
updateBatchQueue.push(result);
|
|
@@ -1327,6 +1442,7 @@ function createEffectiveSetState<T>(
|
|
|
1327
1442
|
newValue: result.newValue ?? null,
|
|
1328
1443
|
itemId: result.itemId,
|
|
1329
1444
|
insertAfterId: result.insertAfterId,
|
|
1445
|
+
metaData: options.metaData,
|
|
1330
1446
|
};
|
|
1331
1447
|
|
|
1332
1448
|
updateBatchQueue.push(newUpdate);
|
|
@@ -1343,10 +1459,8 @@ function createEffectiveSetState<T>(
|
|
|
1343
1459
|
if (latestInitialOptionsRef.current?.middleware) {
|
|
1344
1460
|
latestInitialOptionsRef.current.middleware({ update: newUpdate });
|
|
1345
1461
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
syncApiRef.current.updateState({ operation: newUpdate });
|
|
1349
|
-
}
|
|
1462
|
+
runValidation(newUpdate, options.validationTrigger || 'programmatic');
|
|
1463
|
+
notifyUpdate(newUpdate);
|
|
1350
1464
|
}
|
|
1351
1465
|
}
|
|
1352
1466
|
|
|
@@ -1354,22 +1468,18 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1354
1468
|
stateObject: TStateObject,
|
|
1355
1469
|
{
|
|
1356
1470
|
stateKey,
|
|
1357
|
-
|
|
1358
1471
|
localStorage,
|
|
1359
1472
|
formElements,
|
|
1360
1473
|
reactiveDeps,
|
|
1361
1474
|
reactiveType,
|
|
1362
1475
|
componentId,
|
|
1363
1476
|
defaultState,
|
|
1364
|
-
syncUpdate,
|
|
1365
1477
|
dependencies,
|
|
1366
1478
|
serverState,
|
|
1367
|
-
__useSync,
|
|
1368
1479
|
}: {
|
|
1369
1480
|
stateKey?: string;
|
|
1370
1481
|
componentId?: string;
|
|
1371
1482
|
defaultState?: TStateObject;
|
|
1372
|
-
__useSync?: UseSyncType<TStateObject>;
|
|
1373
1483
|
syncOptions?: SyncOptionsType<any>;
|
|
1374
1484
|
} & OptionsType<TStateObject> = {}
|
|
1375
1485
|
) {
|
|
@@ -1384,16 +1494,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1384
1494
|
latestInitialOptionsRef.current = (getInitialOptions(thisKey as string) ??
|
|
1385
1495
|
null) as OptionsType<TStateObject> | null;
|
|
1386
1496
|
|
|
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
1497
|
const resolveInitialState = useCallback(
|
|
1398
1498
|
(
|
|
1399
1499
|
overrideOptions?: OptionsType<TStateObject>
|
|
@@ -1563,10 +1663,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1563
1663
|
const options = getInitialOptions(thisKey as string);
|
|
1564
1664
|
|
|
1565
1665
|
const features = {
|
|
1566
|
-
syncEnabled: !!cogsSyncFn && !!syncOpt,
|
|
1567
|
-
validationEnabled: !!(
|
|
1568
|
-
options?.validation?.zodSchemaV4 || options?.validation?.zodSchemaV3
|
|
1569
|
-
),
|
|
1570
1666
|
localStorageEnabled: !!options?.localStorage?.key,
|
|
1571
1667
|
};
|
|
1572
1668
|
|
|
@@ -1666,10 +1762,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1666
1762
|
};
|
|
1667
1763
|
}, []);
|
|
1668
1764
|
|
|
1669
|
-
const syncApiRef = useRef<SyncApi | null>(null);
|
|
1670
1765
|
const effectiveSetState = createEffectiveSetState(
|
|
1671
1766
|
thisKey,
|
|
1672
|
-
|
|
1767
|
+
|
|
1673
1768
|
sessionId,
|
|
1674
1769
|
latestInitialOptionsRef
|
|
1675
1770
|
);
|
|
@@ -1689,16 +1784,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1689
1784
|
return handler;
|
|
1690
1785
|
}, [thisKey, sessionId]);
|
|
1691
1786
|
|
|
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
1787
|
return updaterFinal;
|
|
1703
1788
|
}
|
|
1704
1789
|
|
|
@@ -1845,7 +1930,6 @@ function createProxyHandler<T>(
|
|
|
1845
1930
|
sessionId?: string
|
|
1846
1931
|
): StateObject<T> {
|
|
1847
1932
|
const proxyCache = new Map<string, any>();
|
|
1848
|
-
let stateVersion = 0;
|
|
1849
1933
|
|
|
1850
1934
|
function rebuildStateShape({
|
|
1851
1935
|
path = [],
|
|
@@ -1859,6 +1943,7 @@ function createProxyHandler<T>(
|
|
|
1859
1943
|
const derivationSignature = meta
|
|
1860
1944
|
? JSON.stringify(meta.arrayViews || meta.transforms)
|
|
1861
1945
|
: '';
|
|
1946
|
+
|
|
1862
1947
|
const cacheKey =
|
|
1863
1948
|
path.join('.') + ':' + componentId + ':' + derivationSignature;
|
|
1864
1949
|
if (proxyCache.has(cacheKey)) {
|
|
@@ -1871,10 +1956,17 @@ function createProxyHandler<T>(
|
|
|
1871
1956
|
|
|
1872
1957
|
const handler = {
|
|
1873
1958
|
get(target: any, prop: string) {
|
|
1959
|
+
if (typeof prop !== 'string') {
|
|
1960
|
+
// This is a Symbol. Let the default "get" behavior happen.
|
|
1961
|
+
// This allows internal JS operations and dev tools to work correctly
|
|
1962
|
+
// without interfering with your state logic.
|
|
1963
|
+
return Reflect.get(target, prop);
|
|
1964
|
+
}
|
|
1874
1965
|
if (path.length === 0 && prop in rootLevelMethods) {
|
|
1875
1966
|
return rootLevelMethods[prop as keyof typeof rootLevelMethods];
|
|
1876
1967
|
}
|
|
1877
|
-
|
|
1968
|
+
|
|
1969
|
+
if (typeof prop === 'string' && !prop.startsWith('$')) {
|
|
1878
1970
|
const nextPath = [...path, prop];
|
|
1879
1971
|
return rebuildStateShape({
|
|
1880
1972
|
path: nextPath,
|
|
@@ -2800,8 +2892,6 @@ function createProxyHandler<T>(
|
|
|
2800
2892
|
|
|
2801
2893
|
if (!Array.isArray(currentState)) return [];
|
|
2802
2894
|
|
|
2803
|
-
stateVersion++;
|
|
2804
|
-
|
|
2805
2895
|
return rebuildStateShape({
|
|
2806
2896
|
path: [...path, '[*]', fieldName],
|
|
2807
2897
|
componentId: componentId!,
|
|
@@ -2863,7 +2953,18 @@ function createProxyHandler<T>(
|
|
|
2863
2953
|
payload: InsertParams<InferArrayElement<T>>,
|
|
2864
2954
|
index?: number
|
|
2865
2955
|
) => {
|
|
2866
|
-
effectiveSetState(payload as any, path, {
|
|
2956
|
+
effectiveSetState(payload as any, path, {
|
|
2957
|
+
updateType: 'insert',
|
|
2958
|
+
index,
|
|
2959
|
+
});
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
if (prop === '$insertMany') {
|
|
2963
|
+
return (payload: InferArrayElement<T>[]) => {
|
|
2964
|
+
// Call the one true path for all state changes.
|
|
2965
|
+
effectiveSetState(payload as any, path, {
|
|
2966
|
+
updateType: 'insert_many',
|
|
2967
|
+
});
|
|
2867
2968
|
};
|
|
2868
2969
|
}
|
|
2869
2970
|
if (prop === '$uniqueInsert') {
|
|
@@ -2911,6 +3012,7 @@ function createProxyHandler<T>(
|
|
|
2911
3012
|
if (prop === '$cut') {
|
|
2912
3013
|
return (index?: number, options?: { waitForSync?: boolean }) => {
|
|
2913
3014
|
const shadowMeta = getShadowMetadata(stateKey, path);
|
|
3015
|
+
console.log('shadowMeta ->>>>>>>>>>>>>>>>', shadowMeta);
|
|
2914
3016
|
if (!shadowMeta?.arrayKeys || shadowMeta.arrayKeys.length === 0)
|
|
2915
3017
|
return;
|
|
2916
3018
|
|
|
@@ -2921,8 +3023,11 @@ function createProxyHandler<T>(
|
|
|
2921
3023
|
? index
|
|
2922
3024
|
: shadowMeta.arrayKeys.length - 1;
|
|
2923
3025
|
|
|
3026
|
+
console.log('indexToCut ->>>>>>>>>>>>>>>>', indexToCut);
|
|
3027
|
+
|
|
2924
3028
|
const idToCut = shadowMeta.arrayKeys[indexToCut];
|
|
2925
3029
|
if (!idToCut) return;
|
|
3030
|
+
console.log('idToCut ->>>>>>>>>>>>>>>>', idToCut);
|
|
2926
3031
|
|
|
2927
3032
|
effectiveSetState(null, [...path, idToCut], {
|
|
2928
3033
|
updateType: 'cut',
|
|
@@ -3024,11 +3129,7 @@ function createProxyHandler<T>(
|
|
|
3024
3129
|
});
|
|
3025
3130
|
}
|
|
3026
3131
|
|
|
3027
|
-
return
|
|
3028
|
-
path: [...path, `not_found_${uuidv4()}`],
|
|
3029
|
-
componentId: componentId!,
|
|
3030
|
-
meta,
|
|
3031
|
-
});
|
|
3132
|
+
return null;
|
|
3032
3133
|
};
|
|
3033
3134
|
}
|
|
3034
3135
|
if (prop === '$cutThis') {
|
|
@@ -3058,10 +3159,59 @@ function createProxyHandler<T>(
|
|
|
3058
3159
|
});
|
|
3059
3160
|
}
|
|
3060
3161
|
|
|
3162
|
+
// if (prop === '$formInput') {
|
|
3163
|
+
// const _getFormElement = (path: string[]): HTMLElement | null => {
|
|
3164
|
+
// const metadata = getShadowMetadata(stateKey, path);
|
|
3165
|
+
// if (metadata?.formRef?.current) {
|
|
3166
|
+
// return metadata.formRef.current;
|
|
3167
|
+
// }
|
|
3168
|
+
// // This warning is helpful for debugging if a ref is missing.
|
|
3169
|
+
// console.warn(
|
|
3170
|
+
// `Form element ref not found for stateKey "${stateKey}" at path "${path.join('.')}"`
|
|
3171
|
+
// );
|
|
3172
|
+
// return null;
|
|
3173
|
+
// };
|
|
3174
|
+
// return {
|
|
3175
|
+
// setDisabled: (isDisabled: boolean) => {
|
|
3176
|
+
// const element = _getFormElement(path) as HTMLInputElement | null;
|
|
3177
|
+
// if (element) {
|
|
3178
|
+
// element.disabled = isDisabled;
|
|
3179
|
+
// }
|
|
3180
|
+
// },
|
|
3181
|
+
// focus: () => {
|
|
3182
|
+
// const element = _getFormElement(path);
|
|
3183
|
+
// element?.focus();
|
|
3184
|
+
// },
|
|
3185
|
+
// blur: () => {
|
|
3186
|
+
// const element = _getFormElement(path);
|
|
3187
|
+
// element?.blur();
|
|
3188
|
+
// },
|
|
3189
|
+
// scrollIntoView: (options?: ScrollIntoViewOptions) => {
|
|
3190
|
+
// const element = _getFormElement(path);
|
|
3191
|
+
// element?.scrollIntoView(
|
|
3192
|
+
// options ?? { behavior: 'smooth', block: 'center' }
|
|
3193
|
+
// );
|
|
3194
|
+
// },
|
|
3195
|
+
// click: () => {
|
|
3196
|
+
// const element = _getFormElement(path);
|
|
3197
|
+
// element?.click();
|
|
3198
|
+
// },
|
|
3199
|
+
// selectText: () => {
|
|
3200
|
+
// const element = _getFormElement(path) as
|
|
3201
|
+
// | HTMLInputElement
|
|
3202
|
+
// | HTMLTextAreaElement
|
|
3203
|
+
// | null;
|
|
3204
|
+
// if (element && typeof element.select === 'function') {
|
|
3205
|
+
// element.select();
|
|
3206
|
+
// }
|
|
3207
|
+
// },
|
|
3208
|
+
// };
|
|
3209
|
+
// }
|
|
3061
3210
|
if (prop === '$$get') {
|
|
3062
3211
|
return () =>
|
|
3063
3212
|
$cogsSignal({ _stateKey: stateKey, _path: path, _meta: meta });
|
|
3064
3213
|
}
|
|
3214
|
+
|
|
3065
3215
|
if (prop === '$lastSynced') {
|
|
3066
3216
|
const syncKey = `${stateKey}:${path.join('.')}`;
|
|
3067
3217
|
return getSyncInfo(syncKey);
|
|
@@ -3133,6 +3283,38 @@ function createProxyHandler<T>(
|
|
|
3133
3283
|
return componentId;
|
|
3134
3284
|
}
|
|
3135
3285
|
if (path.length == 0) {
|
|
3286
|
+
if (prop === '$setOptions') {
|
|
3287
|
+
return (options: OptionsType<T>) => {
|
|
3288
|
+
setOptions({ stateKey, options, initialOptionsPart: {} });
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
if (prop === '$_applyUpdate') {
|
|
3293
|
+
return (
|
|
3294
|
+
value: any,
|
|
3295
|
+
path: string[],
|
|
3296
|
+
updateType: 'update' | 'insert' | 'cut' = 'update'
|
|
3297
|
+
) => {
|
|
3298
|
+
effectiveSetState(value, path, { updateType });
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
if (prop === '$_getEffectiveSetState') {
|
|
3303
|
+
return effectiveSetState;
|
|
3304
|
+
}
|
|
3305
|
+
if (prop === '$getPluginMetaData') {
|
|
3306
|
+
return (pluginName: string) =>
|
|
3307
|
+
getPluginMetaDataMap(stateKey, path)?.get(pluginName);
|
|
3308
|
+
}
|
|
3309
|
+
if (prop === '$addPluginMetaData') {
|
|
3310
|
+
console.log('$addPluginMetaDat');
|
|
3311
|
+
return (pluginName: string, data: Record<string, any>) =>
|
|
3312
|
+
setPluginMetaData(stateKey, path, pluginName, data);
|
|
3313
|
+
}
|
|
3314
|
+
if (prop === '$removePluginMetaData') {
|
|
3315
|
+
return (pluginName: string) =>
|
|
3316
|
+
removePluginMetaData(stateKey, path, pluginName);
|
|
3317
|
+
}
|
|
3136
3318
|
if (prop === '$addZodValidation') {
|
|
3137
3319
|
return (
|
|
3138
3320
|
zodErrors: any[],
|
|
@@ -3185,46 +3367,67 @@ function createProxyHandler<T>(
|
|
|
3185
3367
|
}
|
|
3186
3368
|
|
|
3187
3369
|
if (prop === '$applyOperation') {
|
|
3188
|
-
return (
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3370
|
+
return (
|
|
3371
|
+
operation: UpdateTypeDetail & {
|
|
3372
|
+
validation?: any[];
|
|
3373
|
+
version?: string;
|
|
3374
|
+
},
|
|
3375
|
+
metaData?: Record<string, any>
|
|
3376
|
+
) => {
|
|
3377
|
+
// const validationErrorsFromServer = operation.validation || [];
|
|
3378
|
+
|
|
3379
|
+
// if (!operation || !operation.path) {
|
|
3380
|
+
// console.error(
|
|
3381
|
+
// 'Invalid operation received by $applyOperation:',
|
|
3382
|
+
// operation
|
|
3383
|
+
// );
|
|
3384
|
+
// return;
|
|
3385
|
+
// }
|
|
3386
|
+
|
|
3387
|
+
// const newErrors: ValidationError[] =
|
|
3388
|
+
// validationErrorsFromServer.map((err) => ({
|
|
3389
|
+
// source: 'sync_engine',
|
|
3390
|
+
// message: err.message,
|
|
3391
|
+
// severity: 'warning',
|
|
3392
|
+
// code: err.code,
|
|
3393
|
+
// }));
|
|
3394
|
+
// console.log('newErrors', newErrors);
|
|
3395
|
+
// getGlobalStore
|
|
3396
|
+
// .getState()
|
|
3397
|
+
// .setShadowMetadata(stateKey, operation.path, {
|
|
3398
|
+
// validation: {
|
|
3399
|
+
// status: newErrors.length > 0 ? 'INVALID' : 'VALID',
|
|
3400
|
+
// errors: newErrors,
|
|
3401
|
+
// lastValidated: Date.now(),
|
|
3402
|
+
// },
|
|
3403
|
+
// });
|
|
3404
|
+
console.log(
|
|
3405
|
+
'getGlobalStore',
|
|
3202
3406
|
getGlobalStore
|
|
3203
3407
|
.getState()
|
|
3204
|
-
.getShadowMetadata(stateKey,
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3408
|
+
.getShadowMetadata(stateKey, operation.path)
|
|
3409
|
+
);
|
|
3410
|
+
let index: number | undefined;
|
|
3411
|
+
if (
|
|
3412
|
+
operation.insertAfterId &&
|
|
3413
|
+
operation.updateType === 'insert'
|
|
3414
|
+
) {
|
|
3415
|
+
const shadowMeta = getShadowMetadata(stateKey, operation.path);
|
|
3416
|
+
if (shadowMeta?.arrayKeys) {
|
|
3417
|
+
const afterIndex = shadowMeta.arrayKeys.indexOf(
|
|
3418
|
+
operation.insertAfterId
|
|
3419
|
+
);
|
|
3420
|
+
if (afterIndex !== -1) {
|
|
3421
|
+
index = afterIndex + 1; // Insert after the found item
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3213
3425
|
|
|
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, {
|
|
3426
|
+
effectiveSetState(operation.newValue, operation.path, {
|
|
3226
3427
|
updateType: operation.updateType,
|
|
3227
|
-
|
|
3428
|
+
itemId: operation.itemId,
|
|
3429
|
+
index, // Pass the calculated index
|
|
3430
|
+
metaData,
|
|
3228
3431
|
});
|
|
3229
3432
|
};
|
|
3230
3433
|
}
|
|
@@ -3322,14 +3525,8 @@ function createProxyHandler<T>(
|
|
|
3322
3525
|
|
|
3323
3526
|
if (prop === '$getComponents')
|
|
3324
3527
|
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
3528
|
}
|
|
3529
|
+
|
|
3333
3530
|
if (prop === '$validationWrapper') {
|
|
3334
3531
|
return ({
|
|
3335
3532
|
children,
|
|
@@ -3349,6 +3546,7 @@ function createProxyHandler<T>(
|
|
|
3349
3546
|
</ValidationWrapper>
|
|
3350
3547
|
);
|
|
3351
3548
|
}
|
|
3549
|
+
|
|
3352
3550
|
if (prop === '$_stateKey') return stateKey;
|
|
3353
3551
|
if (prop === '$_path') return path;
|
|
3354
3552
|
if (prop === '$update') {
|
|
@@ -3469,9 +3667,11 @@ function createProxyHandler<T>(
|
|
|
3469
3667
|
|
|
3470
3668
|
return revertState;
|
|
3471
3669
|
},
|
|
3670
|
+
$initializeAndMergeShadowState: (newState: T) => {
|
|
3671
|
+
initializeAndMergeShadowState(stateKey, newState);
|
|
3672
|
+
notifyComponents(stateKey);
|
|
3673
|
+
},
|
|
3472
3674
|
$updateInitialState: (newState: T) => {
|
|
3473
|
-
stateVersion++;
|
|
3474
|
-
|
|
3475
3675
|
const newUpdaterState = createProxyHandler(
|
|
3476
3676
|
stateKey,
|
|
3477
3677
|
effectiveSetState,
|