cogsbox-state 0.5.471 → 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/README.md +2 -5
- package/dist/CogsState.d.ts +105 -79
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1082 -987
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.jsx +293 -243
- 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 +69 -26
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +436 -152
- 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 +18 -13
- package/src/CogsState.tsx +719 -458
- package/src/Components.tsx +304 -180
- 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 +881 -189
- 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;
|
|
@@ -80,7 +84,11 @@ export type FormElementParams<T> = StateObject<T> & {
|
|
|
80
84
|
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
81
85
|
>
|
|
82
86
|
) => void;
|
|
83
|
-
onBlur?: (
|
|
87
|
+
onBlur?: (
|
|
88
|
+
event: React.FocusEvent<
|
|
89
|
+
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
90
|
+
>
|
|
91
|
+
) => void;
|
|
84
92
|
};
|
|
85
93
|
};
|
|
86
94
|
|
|
@@ -97,37 +105,7 @@ type CutFunctionType<T> = (
|
|
|
97
105
|
) => StateObject<T>;
|
|
98
106
|
|
|
99
107
|
export type InferArrayElement<T> = T extends (infer U)[] ? U : never;
|
|
100
|
-
|
|
101
|
-
| 'concat'
|
|
102
|
-
| 'copyWithin'
|
|
103
|
-
| 'fill'
|
|
104
|
-
| 'find'
|
|
105
|
-
| 'findIndex'
|
|
106
|
-
| 'flat'
|
|
107
|
-
| 'flatMap'
|
|
108
|
-
| 'includes'
|
|
109
|
-
| 'indexOf'
|
|
110
|
-
| 'join'
|
|
111
|
-
| 'keys'
|
|
112
|
-
| 'lastIndexOf'
|
|
113
|
-
| 'map'
|
|
114
|
-
| 'pop'
|
|
115
|
-
| 'push'
|
|
116
|
-
| 'reduce'
|
|
117
|
-
| 'reduceRight'
|
|
118
|
-
| 'reverse'
|
|
119
|
-
| 'shift'
|
|
120
|
-
| 'slice'
|
|
121
|
-
| 'some'
|
|
122
|
-
| 'sort'
|
|
123
|
-
| 'splice'
|
|
124
|
-
| 'unshift'
|
|
125
|
-
| 'values'
|
|
126
|
-
| 'entries'
|
|
127
|
-
| 'every'
|
|
128
|
-
| 'filter'
|
|
129
|
-
| 'forEach'
|
|
130
|
-
| 'with';
|
|
108
|
+
|
|
131
109
|
export type StreamOptions<T, R = T> = {
|
|
132
110
|
bufferSize?: number;
|
|
133
111
|
flushInterval?: number;
|
|
@@ -158,6 +136,7 @@ export type ArrayEndType<TShape extends unknown> = {
|
|
|
158
136
|
$_index: number;
|
|
159
137
|
} & EndType<Prettify<InferArrayElement<TShape>>>;
|
|
160
138
|
$insert: InsertType<Prettify<InferArrayElement<TShape>>>;
|
|
139
|
+
$insertMany: (payload: InferArrayElement<TShape>[]) => void;
|
|
161
140
|
$cut: CutFunctionType<TShape>;
|
|
162
141
|
$cutSelected: () => void;
|
|
163
142
|
$cutByValue: (value: string | number | boolean) => void;
|
|
@@ -243,8 +222,20 @@ export type InsertTypeObj<T> = (payload: InsertParams<T>) => void;
|
|
|
243
222
|
|
|
244
223
|
type EffectFunction<T, R> = (state: T, deps: any[]) => R;
|
|
245
224
|
export type EndType<T, IsArrayElement = false> = {
|
|
246
|
-
$
|
|
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
|
+
|
|
230
|
+
$addZodValidation: (
|
|
231
|
+
errors: ValidationError[],
|
|
232
|
+
source?: 'client' | 'sync_engine' | 'api'
|
|
233
|
+
) => void;
|
|
247
234
|
$clearZodValidation: (paths?: string[]) => void;
|
|
235
|
+
$applyOperation: (
|
|
236
|
+
operation: UpdateTypeDetail,
|
|
237
|
+
metaData?: Record<string, any>
|
|
238
|
+
) => void;
|
|
248
239
|
$applyJsonPatch: (patches: any[]) => void;
|
|
249
240
|
$update: UpdateType<T>;
|
|
250
241
|
$_path: string[];
|
|
@@ -265,7 +256,14 @@ export type EndType<T, IsArrayElement = false> = {
|
|
|
265
256
|
$isSelected: boolean;
|
|
266
257
|
$setSelected: (value: boolean) => void;
|
|
267
258
|
$toggleSelected: () => void;
|
|
268
|
-
$
|
|
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
|
+
};
|
|
269
267
|
$removeStorage: () => void;
|
|
270
268
|
$sync: () => void;
|
|
271
269
|
$validationWrapper: ({
|
|
@@ -287,7 +285,7 @@ export type StateObject<T> = (T extends any[]
|
|
|
287
285
|
: never) &
|
|
288
286
|
EndType<T, true> & {
|
|
289
287
|
$toggle: T extends boolean ? () => void : never;
|
|
290
|
-
|
|
288
|
+
|
|
291
289
|
$_componentId: string | null;
|
|
292
290
|
$getComponents: () => ComponentsType;
|
|
293
291
|
|
|
@@ -295,6 +293,7 @@ export type StateObject<T> = (T extends any[]
|
|
|
295
293
|
$updateInitialState: (newState: T | null) => {
|
|
296
294
|
fetchId: (field: keyof T) => string | number;
|
|
297
295
|
};
|
|
296
|
+
$initializeAndMergeShadowState: (newState: any | null) => void;
|
|
298
297
|
$_isLoading: boolean;
|
|
299
298
|
$_serverState: T;
|
|
300
299
|
$revertToInitialState: (obj?: { validationKey?: string }) => T;
|
|
@@ -321,10 +320,13 @@ type EffectiveSetStateArg<
|
|
|
321
320
|
? InsertParams<InferArrayElement<T>>
|
|
322
321
|
: never
|
|
323
322
|
: UpdateArg<T>;
|
|
324
|
-
type UpdateOptions = {
|
|
325
|
-
updateType: 'insert' | 'cut' | 'update';
|
|
326
323
|
|
|
327
|
-
|
|
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>;
|
|
328
330
|
};
|
|
329
331
|
type EffectiveSetState<TStateObject> = (
|
|
330
332
|
newStateOrFunction:
|
|
@@ -332,21 +334,24 @@ type EffectiveSetState<TStateObject> = (
|
|
|
332
334
|
| EffectiveSetStateArg<TStateObject, 'insert'>
|
|
333
335
|
| null,
|
|
334
336
|
path: string[],
|
|
335
|
-
updateObj: UpdateOptions,
|
|
337
|
+
updateObj: UpdateOptions, // Now includes itemId
|
|
336
338
|
validationKey?: string
|
|
337
339
|
) => void;
|
|
338
340
|
|
|
339
341
|
export type UpdateTypeDetail = {
|
|
340
342
|
timeStamp: number;
|
|
341
343
|
stateKey: string;
|
|
342
|
-
updateType: 'update' | 'insert' | 'cut';
|
|
344
|
+
updateType: 'update' | 'insert' | 'cut' | 'insert_many';
|
|
343
345
|
path: string[];
|
|
344
346
|
status: 'new' | 'sent' | 'synced';
|
|
345
347
|
oldValue: any;
|
|
346
348
|
newValue: any;
|
|
347
349
|
userId?: number;
|
|
348
|
-
};
|
|
349
350
|
|
|
351
|
+
itemId?: string;
|
|
352
|
+
insertAfterId?: string;
|
|
353
|
+
metaData?: Record<string, any>;
|
|
354
|
+
};
|
|
350
355
|
export type ReactivityUnion = 'none' | 'component' | 'deps' | 'all';
|
|
351
356
|
export type ReactivityType =
|
|
352
357
|
| 'none'
|
|
@@ -356,20 +361,16 @@ export type ReactivityType =
|
|
|
356
361
|
| Array<Prettify<'none' | 'component' | 'deps' | 'all'>>;
|
|
357
362
|
|
|
358
363
|
// Define the return type of the sync hook locally
|
|
359
|
-
|
|
360
|
-
updateState: (data: { operation: any }) => void;
|
|
361
|
-
connected: boolean;
|
|
362
|
-
clientId: string | null;
|
|
363
|
-
subscribers: string[];
|
|
364
|
-
};
|
|
364
|
+
|
|
365
365
|
type ValidationOptionsType = {
|
|
366
366
|
key?: string;
|
|
367
367
|
zodSchemaV3?: z3.ZodType<any, any, any>;
|
|
368
368
|
zodSchemaV4?: z4.ZodType<any, any, any>;
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
onBlur?: 'error' | 'warning';
|
|
370
|
+
onChange?: 'error' | 'warning';
|
|
371
|
+
blockSync?: boolean;
|
|
371
372
|
};
|
|
372
|
-
|
|
373
|
+
|
|
373
374
|
type SyncOptionsType<TApiParams> = {
|
|
374
375
|
apiParams: TApiParams;
|
|
375
376
|
stateKey?: string;
|
|
@@ -380,12 +381,23 @@ type SyncOptionsType<TApiParams> = {
|
|
|
380
381
|
connect?: boolean;
|
|
381
382
|
inMemoryState?: boolean;
|
|
382
383
|
};
|
|
383
|
-
|
|
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 & {
|
|
384
397
|
log?: boolean;
|
|
385
398
|
componentId?: string;
|
|
386
399
|
syncOptions?: SyncOptionsType<TApiParams>;
|
|
387
400
|
|
|
388
|
-
validation?: ValidationOptionsType;
|
|
389
401
|
serverState?: {
|
|
390
402
|
id?: string | number;
|
|
391
403
|
data?: T;
|
|
@@ -414,12 +426,10 @@ export type OptionsType<T extends unknown = unknown, TApiParams = never> = {
|
|
|
414
426
|
};
|
|
415
427
|
middleware?: ({ update }: { update: UpdateTypeDetail }) => void;
|
|
416
428
|
|
|
417
|
-
modifyState?: (state: T) => T;
|
|
418
429
|
localStorage?: {
|
|
419
430
|
key: string | ((state: T) => string);
|
|
420
431
|
onChange?: (state: T) => void;
|
|
421
432
|
};
|
|
422
|
-
formElements?: FormsElementsType<T>;
|
|
423
433
|
|
|
424
434
|
reactiveDeps?: (state: T) => any[] | true;
|
|
425
435
|
reactiveType?: ReactivityType;
|
|
@@ -430,14 +440,11 @@ export type OptionsType<T extends unknown = unknown, TApiParams = never> = {
|
|
|
430
440
|
dependencies?: any[];
|
|
431
441
|
};
|
|
432
442
|
|
|
433
|
-
export type
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
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.
|
|
441
448
|
validation?: (options: {
|
|
442
449
|
children: React.ReactNode;
|
|
443
450
|
status: ValidationStatus;
|
|
@@ -445,28 +452,32 @@ type FormsElementsType<T> = {
|
|
|
445
452
|
hasErrors: boolean;
|
|
446
453
|
hasWarnings: boolean;
|
|
447
454
|
allErrors: ValidationError[];
|
|
448
|
-
|
|
449
455
|
path: string[];
|
|
450
456
|
message?: string;
|
|
451
|
-
getData?: () =>
|
|
457
|
+
getData?: () => TState;
|
|
452
458
|
}) => React.ReactNode;
|
|
453
|
-
syncRender?: (options:
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
[
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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;
|
|
469
475
|
};
|
|
476
|
+
export type CogsInitialState<T> =
|
|
477
|
+
| {
|
|
478
|
+
initialState: T;
|
|
479
|
+
}
|
|
480
|
+
| CreateStateOptionsType<T>;
|
|
470
481
|
|
|
471
482
|
export type TransformedStateType<T> = {
|
|
472
483
|
[P in keyof T]: T[P] extends CogsInitialState<infer U> ? U : T[P];
|
|
@@ -479,6 +490,7 @@ const {
|
|
|
479
490
|
setShadowMetadata,
|
|
480
491
|
getShadowValue,
|
|
481
492
|
initializeShadowState,
|
|
493
|
+
initializeAndMergeShadowState,
|
|
482
494
|
updateShadowAtPath,
|
|
483
495
|
insertShadowArrayElement,
|
|
484
496
|
insertManyShadowArrayElements,
|
|
@@ -493,8 +505,14 @@ const {
|
|
|
493
505
|
clearSelectedIndex,
|
|
494
506
|
getSyncInfo,
|
|
495
507
|
notifyPathSubscribers,
|
|
508
|
+
getPluginMetaDataMap,
|
|
509
|
+
setPluginMetaData,
|
|
510
|
+
removePluginMetaData,
|
|
496
511
|
// Note: The old functions are no longer imported under their original names
|
|
497
512
|
} = getGlobalStore.getState();
|
|
513
|
+
|
|
514
|
+
const { notifyUpdate } = pluginStore.getState();
|
|
515
|
+
|
|
498
516
|
function getArrayData(stateKey: string, path: string[], meta?: MetaData) {
|
|
499
517
|
const shadowMeta = getShadowMetadata(stateKey, path);
|
|
500
518
|
const isArray = !!shadowMeta?.arrayKeys;
|
|
@@ -537,11 +555,23 @@ function findArrayItem(
|
|
|
537
555
|
function setAndMergeOptions(stateKey: string, newOptions: OptionsType<any>) {
|
|
538
556
|
const initialOptions = getInitialOptions(stateKey as string) || {};
|
|
539
557
|
|
|
540
|
-
|
|
558
|
+
const mergedOptions = {
|
|
541
559
|
...initialOptions,
|
|
542
560
|
...newOptions,
|
|
543
|
-
}
|
|
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);
|
|
544
573
|
}
|
|
574
|
+
|
|
545
575
|
function setOptions<StateKey, Opt>({
|
|
546
576
|
stateKey,
|
|
547
577
|
options,
|
|
@@ -554,92 +584,134 @@ function setOptions<StateKey, Opt>({
|
|
|
554
584
|
const initialOptions = getInitialOptions(stateKey as string) || {};
|
|
555
585
|
const initialOptionsPartState = initialOptionsPart[stateKey as string] || {};
|
|
556
586
|
|
|
557
|
-
|
|
558
|
-
|
|
587
|
+
let mergedOptions = { ...initialOptionsPartState, ...initialOptions };
|
|
559
588
|
let needToAdd = false;
|
|
589
|
+
|
|
560
590
|
if (options) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
) {
|
|
580
|
-
needToAdd = true;
|
|
581
|
-
mergedOptions[key] = options[key];
|
|
591
|
+
const deepMerge = (target: any, source: any) => {
|
|
592
|
+
for (const key in source) {
|
|
593
|
+
if (source.hasOwnProperty(key)) {
|
|
594
|
+
if (
|
|
595
|
+
source[key] instanceof Object &&
|
|
596
|
+
!Array.isArray(source[key]) &&
|
|
597
|
+
target[key] instanceof Object
|
|
598
|
+
) {
|
|
599
|
+
if (!isDeepEqual(target[key], source[key])) {
|
|
600
|
+
deepMerge(target[key], source[key]);
|
|
601
|
+
needToAdd = true;
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
if (target[key] !== source[key]) {
|
|
605
|
+
target[key] = source[key];
|
|
606
|
+
needToAdd = true;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
582
609
|
}
|
|
583
610
|
}
|
|
584
|
-
|
|
611
|
+
return target;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
mergedOptions = deepMerge(mergedOptions, options);
|
|
585
615
|
}
|
|
586
616
|
|
|
587
|
-
//
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
617
|
+
// Set default onBlur
|
|
618
|
+
if (mergedOptions.validation) {
|
|
619
|
+
const onBlurProvided =
|
|
620
|
+
options?.validation?.hasOwnProperty('onBlur') ||
|
|
621
|
+
initialOptions?.validation?.hasOwnProperty('onBlur') ||
|
|
622
|
+
initialOptionsPartState?.validation?.hasOwnProperty('onBlur');
|
|
623
|
+
|
|
624
|
+
if (!onBlurProvided) {
|
|
625
|
+
mergedOptions.validation.onBlur = 'error';
|
|
626
|
+
needToAdd = true;
|
|
627
|
+
}
|
|
593
628
|
}
|
|
594
629
|
|
|
595
630
|
if (needToAdd) {
|
|
596
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
|
+
}
|
|
597
662
|
}
|
|
598
|
-
}
|
|
599
663
|
|
|
600
|
-
|
|
664
|
+
return mergedOptions;
|
|
665
|
+
}
|
|
666
|
+
export function addStateOptions<T>(
|
|
601
667
|
initialState: T,
|
|
602
|
-
|
|
668
|
+
options: CreateStateOptionsType<T>
|
|
603
669
|
) {
|
|
604
|
-
return {
|
|
670
|
+
return {
|
|
671
|
+
...options,
|
|
672
|
+
initialState,
|
|
673
|
+
_addStateOptions: true,
|
|
674
|
+
};
|
|
605
675
|
}
|
|
676
|
+
export type PluginData = {
|
|
677
|
+
plugin: CogsPlugin<any, any, any, any, any>;
|
|
678
|
+
options: any;
|
|
679
|
+
hookData?: any;
|
|
680
|
+
};
|
|
606
681
|
|
|
607
|
-
|
|
608
|
-
type SetCogsOptionsFunc<T extends Record<string, any>> = <
|
|
609
|
-
StateKey extends keyof TransformedStateType<T>,
|
|
610
|
-
>(
|
|
611
|
-
stateKey: StateKey,
|
|
612
|
-
options: OptionsType<TransformedStateType<T>[StateKey]>
|
|
613
|
-
) => void;
|
|
682
|
+
////////////////////////////////
|
|
614
683
|
|
|
615
|
-
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
|
+
>(
|
|
616
688
|
initialState: State,
|
|
617
689
|
opt?: {
|
|
618
|
-
|
|
690
|
+
plugins?: TPlugins;
|
|
691
|
+
formElements?: FormsElementsType<State, TPlugins>;
|
|
619
692
|
validation?: ValidationOptionsType;
|
|
620
|
-
__fromSyncSchema?: boolean;
|
|
621
|
-
__syncNotifications?: Record<string, Function>;
|
|
622
|
-
__apiParamsMap?: Record<string, any>;
|
|
623
|
-
__useSync?: UseSyncType<State>;
|
|
624
|
-
__syncSchemas?: Record<string, any>;
|
|
625
693
|
}
|
|
626
694
|
) => {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
+
};
|
|
630
706
|
|
|
631
|
-
if (opt?.
|
|
632
|
-
|
|
633
|
-
.getState()
|
|
634
|
-
.setInitialStateOptions('__notifications', opt.__syncNotifications);
|
|
707
|
+
if (opt?.plugins) {
|
|
708
|
+
pluginStore.getState().setRegisteredPlugins(opt.plugins as any);
|
|
635
709
|
}
|
|
636
710
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
.getState()
|
|
640
|
-
.setInitialStateOptions('__apiParamsMap', opt.__apiParamsMap);
|
|
641
|
-
}
|
|
711
|
+
const [statePart, initialOptionsPart] =
|
|
712
|
+
transformStateFunc<State>(initialState);
|
|
642
713
|
|
|
714
|
+
// FIX: Store options INCLUDING validation for each state key
|
|
643
715
|
Object.keys(statePart).forEach((key) => {
|
|
644
716
|
let existingOptions = initialOptionsPart[key] || {};
|
|
645
717
|
|
|
@@ -654,61 +726,77 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
654
726
|
};
|
|
655
727
|
}
|
|
656
728
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
729
|
+
mergedOptions.validation = {
|
|
730
|
+
onBlur: 'error',
|
|
731
|
+
...opt?.validation,
|
|
732
|
+
...(existingOptions.validation || {}),
|
|
733
|
+
};
|
|
662
734
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
if (opt?.__syncSchemas?.[key]?.schemas?.validation) {
|
|
668
|
-
mergedOptions.validation = {
|
|
669
|
-
zodSchemaV4: opt.__syncSchemas[key].schemas.validation,
|
|
670
|
-
...existingOptions.validation,
|
|
671
|
-
};
|
|
735
|
+
if (opt?.validation?.key && !existingOptions.validation?.key) {
|
|
736
|
+
mergedOptions.validation.key = `${opt.validation.key}.${key}`;
|
|
672
737
|
}
|
|
673
|
-
if (Object.keys(mergedOptions).length > 0) {
|
|
674
|
-
const existingGlobalOptions = getInitialOptions(key);
|
|
675
738
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
setInitialStateOptions(key, {
|
|
739
|
+
const existingGlobalOptions = getInitialOptions(key);
|
|
740
|
+
|
|
741
|
+
const finalOptions = existingGlobalOptions
|
|
742
|
+
? {
|
|
681
743
|
...existingGlobalOptions,
|
|
682
744
|
...mergedOptions,
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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);
|
|
686
757
|
});
|
|
687
758
|
|
|
688
759
|
Object.keys(statePart).forEach((key) => {
|
|
689
760
|
initializeShadowState(key, statePart[key]);
|
|
690
761
|
});
|
|
691
|
-
|
|
762
|
+
type Prettify<T> = {
|
|
763
|
+
[K in keyof T]: T[K];
|
|
764
|
+
} & {};
|
|
692
765
|
type StateKeys = keyof typeof statePart;
|
|
693
766
|
|
|
694
767
|
const useCogsState = <StateKey extends StateKeys>(
|
|
695
768
|
stateKey: StateKey,
|
|
696
|
-
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
|
+
>
|
|
697
784
|
) => {
|
|
698
785
|
const [componentId] = useState(options?.componentId ?? uuidv4());
|
|
699
786
|
|
|
700
|
-
setOptions({
|
|
787
|
+
const currentOptions = setOptions({
|
|
701
788
|
stateKey,
|
|
702
789
|
options,
|
|
703
790
|
initialOptionsPart,
|
|
704
791
|
});
|
|
792
|
+
|
|
793
|
+
const optionsRef = useRef(currentOptions);
|
|
794
|
+
optionsRef.current = currentOptions;
|
|
795
|
+
|
|
705
796
|
const thiState =
|
|
706
797
|
getShadowValue(stateKey as string, []) || statePart[stateKey as string];
|
|
707
|
-
const partialState = options?.modifyState
|
|
708
|
-
? options.modifyState(thiState)
|
|
709
|
-
: thiState;
|
|
710
798
|
|
|
711
|
-
const updater = useCogsStateFn<(typeof statePart)[StateKey]>(
|
|
799
|
+
const updater = useCogsStateFn<(typeof statePart)[StateKey]>(thiState, {
|
|
712
800
|
stateKey: stateKey as string,
|
|
713
801
|
syncUpdate: options?.syncUpdate,
|
|
714
802
|
componentId,
|
|
@@ -719,14 +807,30 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
719
807
|
defaultState: options?.defaultState as any,
|
|
720
808
|
dependencies: options?.dependencies,
|
|
721
809
|
serverState: options?.serverState,
|
|
722
|
-
syncOptions: options?.syncOptions,
|
|
723
|
-
__useSync: opt?.__useSync as UseSyncType<(typeof statePart)[StateKey]>,
|
|
724
810
|
});
|
|
725
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
|
+
|
|
726
830
|
return updater;
|
|
727
831
|
};
|
|
728
832
|
|
|
729
|
-
function
|
|
833
|
+
function setCogsOptionsByKey<StateKey extends StateKeys>(
|
|
730
834
|
stateKey: StateKey,
|
|
731
835
|
options: OptionsType<(typeof statePart)[StateKey]>
|
|
732
836
|
) {
|
|
@@ -739,93 +843,53 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
739
843
|
notifyComponents(stateKey as string);
|
|
740
844
|
}
|
|
741
845
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
> & {
|
|
759
|
-
syncOptions: Prettify<SyncOptionsType<TApiParamsMap[StateKey]>>;
|
|
760
|
-
}
|
|
761
|
-
>
|
|
762
|
-
: Prettify<OptionsType<TransformedStateType<T>[StateKey]>>
|
|
763
|
-
) => StateObject<TransformedStateType<T>[StateKey]>;
|
|
764
|
-
|
|
765
|
-
// Update CogsApi to default to never instead of Record<string, never>
|
|
766
|
-
type CogsApi<
|
|
767
|
-
T extends Record<string, any>,
|
|
768
|
-
TApiParamsMap extends Record<string, any> = never,
|
|
769
|
-
> = {
|
|
770
|
-
useCogsState: UseCogsStateHook<T, TApiParamsMap>;
|
|
771
|
-
setCogsOptions: SetCogsOptionsFunc<T>;
|
|
772
|
-
};
|
|
773
|
-
type GetParamType<SchemaEntry> = SchemaEntry extends {
|
|
774
|
-
api?: { queryData?: { _paramType?: infer P } };
|
|
775
|
-
}
|
|
776
|
-
? P
|
|
777
|
-
: never;
|
|
778
|
-
|
|
779
|
-
export function createCogsStateFromSync<
|
|
780
|
-
TSyncSchema extends {
|
|
781
|
-
schemas: Record<
|
|
782
|
-
string,
|
|
783
|
-
{
|
|
784
|
-
schemas: { defaultValues: any };
|
|
785
|
-
api?: {
|
|
786
|
-
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],
|
|
787
862
|
};
|
|
788
|
-
[key: string]: any;
|
|
789
863
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
>(
|
|
794
|
-
syncSchema: TSyncSchema,
|
|
795
|
-
useSync: UseSyncType<any>
|
|
796
|
-
): CogsApi<
|
|
797
|
-
{
|
|
798
|
-
[K in keyof TSyncSchema['schemas']]: TSyncSchema['schemas'][K]['schemas']['defaultValues'];
|
|
799
|
-
},
|
|
800
|
-
{
|
|
801
|
-
[K in keyof TSyncSchema['schemas']]: GetParamType<
|
|
802
|
-
TSyncSchema['schemas'][K]
|
|
803
|
-
>;
|
|
804
|
-
}
|
|
805
|
-
> {
|
|
806
|
-
const schemas = syncSchema.schemas;
|
|
807
|
-
const initialState: any = {};
|
|
808
|
-
const apiParamsMap: any = {};
|
|
809
|
-
|
|
810
|
-
// Extract defaultValues AND apiParams from each entry
|
|
811
|
-
for (const key in schemas) {
|
|
812
|
-
const entry = schemas[key];
|
|
813
|
-
initialState[key] = entry?.schemas?.defaultValues || {};
|
|
814
|
-
|
|
815
|
-
// Extract apiParams from the api.queryData._paramType
|
|
816
|
-
if (entry?.api?.queryData?._paramType) {
|
|
817
|
-
apiParamsMap[key] = entry.api.queryData._paramType;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
864
|
+
// If there's no new wrapper for this plugin, return the original object.
|
|
865
|
+
return plugin;
|
|
866
|
+
});
|
|
820
867
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
+
});
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
useCogsState,
|
|
889
|
+
setCogsOptionsByKey,
|
|
890
|
+
setCogsFormElements,
|
|
891
|
+
};
|
|
892
|
+
};
|
|
829
893
|
|
|
830
894
|
const saveToLocalStorage = <T,>(
|
|
831
895
|
state: T,
|
|
@@ -997,35 +1061,14 @@ let isFlushScheduled = false;
|
|
|
997
1061
|
function scheduleFlush() {
|
|
998
1062
|
if (!isFlushScheduled) {
|
|
999
1063
|
isFlushScheduled = true;
|
|
1000
|
-
|
|
1064
|
+
console.log('Scheduling flush');
|
|
1065
|
+
queueMicrotask(() => {
|
|
1066
|
+
console.log('Actually flushing');
|
|
1067
|
+
flushQueue();
|
|
1068
|
+
});
|
|
1001
1069
|
}
|
|
1002
1070
|
}
|
|
1003
|
-
function handleUpdate(
|
|
1004
|
-
stateKey: string,
|
|
1005
|
-
path: string[],
|
|
1006
|
-
payload: any
|
|
1007
|
-
): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
|
|
1008
|
-
// ✅ FIX: Get the old value before the update.
|
|
1009
|
-
const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
|
|
1010
|
-
|
|
1011
|
-
const newValue = isFunction(payload) ? payload(oldValue) : payload;
|
|
1012
|
-
|
|
1013
|
-
// ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
|
|
1014
|
-
// The manual loop has been removed.
|
|
1015
|
-
updateShadowAtPath(stateKey, path, newValue);
|
|
1016
|
-
|
|
1017
|
-
markAsDirty(stateKey, path, { bubble: true });
|
|
1018
1071
|
|
|
1019
|
-
// Return the metadata of the node *after* the update.
|
|
1020
|
-
const newShadowMeta = getShadowMetadata(stateKey, path);
|
|
1021
|
-
|
|
1022
|
-
return {
|
|
1023
|
-
type: 'update',
|
|
1024
|
-
oldValue: oldValue,
|
|
1025
|
-
newValue,
|
|
1026
|
-
shadowMeta: newShadowMeta,
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
1072
|
// 2. Update signals
|
|
1030
1073
|
function updateSignals(shadowMeta: any, displayValue: any) {
|
|
1031
1074
|
if (!shadowMeta?.signals?.length) return;
|
|
@@ -1127,79 +1170,165 @@ function getComponentNotifications(
|
|
|
1127
1170
|
}
|
|
1128
1171
|
});
|
|
1129
1172
|
}
|
|
1130
|
-
} else if (
|
|
1131
|
-
|
|
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'])
|
|
1132
1179
|
const parentArrayPath = result.type === 'insert' ? path : path.slice(0, -1);
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if (currentPath.length === 0) break;
|
|
1197
|
+
currentPath.pop(); // Go up one level
|
|
1143
1198
|
}
|
|
1199
|
+
// --- END FIX ---
|
|
1144
1200
|
}
|
|
1145
1201
|
|
|
1146
1202
|
// --- PASS 2: Handle 'all' and 'deps' reactivity types ---
|
|
1147
1203
|
// Iterate over all components for this stateKey that haven't been notified yet.
|
|
1148
|
-
rootMeta.components.forEach((component, componentId) => {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
});
|
|
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
|
+
// });
|
|
1173
1229
|
|
|
1174
1230
|
return componentsToNotify;
|
|
1175
1231
|
}
|
|
1176
1232
|
|
|
1177
|
-
function
|
|
1233
|
+
function handleUpdate(
|
|
1178
1234
|
stateKey: string,
|
|
1179
1235
|
path: string[],
|
|
1180
1236
|
payload: any
|
|
1181
|
-
): { type: '
|
|
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
|
+
}
|
|
1282
|
+
function handleInsert(
|
|
1283
|
+
stateKey: string,
|
|
1284
|
+
path: string[],
|
|
1285
|
+
payload: any,
|
|
1286
|
+
index?: number,
|
|
1287
|
+
itemId?: string
|
|
1288
|
+
): {
|
|
1289
|
+
type: 'insert';
|
|
1290
|
+
newValue: any;
|
|
1291
|
+
shadowMeta: any;
|
|
1292
|
+
path: string[];
|
|
1293
|
+
itemId: string;
|
|
1294
|
+
insertAfterId?: string;
|
|
1295
|
+
} {
|
|
1182
1296
|
let newValue;
|
|
1297
|
+
|
|
1183
1298
|
if (isFunction(payload)) {
|
|
1184
1299
|
const { value: currentValue } = getScopedData(stateKey, path);
|
|
1185
|
-
newValue = payload({ state: currentValue
|
|
1300
|
+
newValue = payload({ state: currentValue });
|
|
1186
1301
|
} else {
|
|
1187
1302
|
newValue = payload;
|
|
1188
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');
|
|
1189
1314
|
|
|
1190
|
-
insertShadowArrayElement(stateKey, path, newValue);
|
|
1191
1315
|
markAsDirty(stateKey, path, { bubble: true });
|
|
1192
1316
|
|
|
1193
1317
|
const updatedMeta = getShadowMetadata(stateKey, path);
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
markAsDirty(stateKey, newItemPath, { bubble: false });
|
|
1199
|
-
}
|
|
1318
|
+
|
|
1319
|
+
let insertAfterId: string | undefined;
|
|
1320
|
+
if (updatedMeta?.arrayKeys && index !== undefined && index > 0) {
|
|
1321
|
+
insertAfterId = updatedMeta.arrayKeys[index - 1];
|
|
1200
1322
|
}
|
|
1201
1323
|
|
|
1202
|
-
return {
|
|
1324
|
+
return {
|
|
1325
|
+
type: 'insert',
|
|
1326
|
+
newValue,
|
|
1327
|
+
shadowMeta: updatedMeta,
|
|
1328
|
+
path: path,
|
|
1329
|
+
itemId: actualItemId,
|
|
1330
|
+
insertAfterId: insertAfterId,
|
|
1331
|
+
};
|
|
1203
1332
|
}
|
|
1204
1333
|
|
|
1205
1334
|
function handleCut(
|
|
@@ -1231,7 +1360,7 @@ function flushQueue() {
|
|
|
1231
1360
|
if (result.shadowMeta?.signals?.length > 0) {
|
|
1232
1361
|
signalUpdates.push({ shadowMeta: result.shadowMeta, displayValue });
|
|
1233
1362
|
}
|
|
1234
|
-
|
|
1363
|
+
// console.time('getComponentNotifications');
|
|
1235
1364
|
const componentNotifications = getComponentNotifications(
|
|
1236
1365
|
result.stateKey,
|
|
1237
1366
|
result.path,
|
|
@@ -1241,60 +1370,64 @@ function flushQueue() {
|
|
|
1241
1370
|
componentNotifications.forEach((component) => {
|
|
1242
1371
|
allComponentsToNotify.add(component);
|
|
1243
1372
|
});
|
|
1373
|
+
// console.timeEnd('getComponentNotifications');
|
|
1244
1374
|
}
|
|
1245
|
-
|
|
1375
|
+
//console.time('logs');
|
|
1246
1376
|
if (logsToAdd.length > 0) {
|
|
1247
1377
|
addStateLog(logsToAdd);
|
|
1248
1378
|
}
|
|
1249
|
-
|
|
1379
|
+
//console.timeEnd('logs');
|
|
1250
1380
|
signalUpdates.forEach(({ shadowMeta, displayValue }) => {
|
|
1251
1381
|
updateSignals(shadowMeta, displayValue);
|
|
1252
1382
|
});
|
|
1253
1383
|
|
|
1384
|
+
// console.time('updateComponents');
|
|
1254
1385
|
allComponentsToNotify.forEach((component) => {
|
|
1255
1386
|
component.forceUpdate();
|
|
1256
1387
|
});
|
|
1388
|
+
//console.timeEnd('updateComponents');
|
|
1257
1389
|
|
|
1258
1390
|
// --- Step 3: CLEANUP ---
|
|
1259
1391
|
// Clear the queue for the next batch of updates.
|
|
1260
1392
|
updateBatchQueue = [];
|
|
1261
1393
|
isFlushScheduled = false;
|
|
1262
1394
|
}
|
|
1263
|
-
|
|
1264
1395
|
function createEffectiveSetState<T>(
|
|
1265
1396
|
thisKey: string,
|
|
1266
|
-
syncApiRef: React.MutableRefObject<any>,
|
|
1267
1397
|
sessionId: string | undefined,
|
|
1268
1398
|
latestInitialOptionsRef: React.MutableRefObject<OptionsType<T> | null>
|
|
1269
1399
|
): EffectiveSetState<T> {
|
|
1270
|
-
|
|
1271
|
-
// It is now much simpler, delegating all work to the executeUpdate function.
|
|
1272
|
-
return (newStateOrFunction, path, updateObj, validationKey?) => {
|
|
1400
|
+
return (newStateOrFunction, path, updateObj) => {
|
|
1273
1401
|
executeUpdate(thisKey, path, newStateOrFunction, updateObj);
|
|
1274
1402
|
};
|
|
1275
1403
|
|
|
1276
|
-
// This inner function handles the logic for a single state update.
|
|
1277
1404
|
function executeUpdate(
|
|
1278
1405
|
stateKey: string,
|
|
1279
1406
|
path: string[],
|
|
1280
1407
|
payload: any,
|
|
1281
|
-
options: UpdateOptions
|
|
1408
|
+
options: UpdateOptions // Now includes itemId
|
|
1282
1409
|
) {
|
|
1283
|
-
// --- Step 1: Execute the core state change (Synchronous & Fast) ---
|
|
1284
|
-
// This part modifies the in-memory state representation immediately.
|
|
1285
1410
|
let result: any;
|
|
1286
1411
|
switch (options.updateType) {
|
|
1287
1412
|
case 'update':
|
|
1288
1413
|
result = handleUpdate(stateKey, path, payload);
|
|
1289
1414
|
break;
|
|
1290
1415
|
case 'insert':
|
|
1291
|
-
result = handleInsert(
|
|
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);
|
|
1292
1426
|
break;
|
|
1293
1427
|
case 'cut':
|
|
1294
1428
|
result = handleCut(stateKey, path);
|
|
1295
1429
|
break;
|
|
1296
1430
|
}
|
|
1297
|
-
|
|
1298
1431
|
result.stateKey = stateKey;
|
|
1299
1432
|
result.path = path;
|
|
1300
1433
|
updateBatchQueue.push(result);
|
|
@@ -1308,6 +1441,9 @@ function createEffectiveSetState<T>(
|
|
|
1308
1441
|
status: 'new',
|
|
1309
1442
|
oldValue: result.oldValue,
|
|
1310
1443
|
newValue: result.newValue ?? null,
|
|
1444
|
+
itemId: result.itemId,
|
|
1445
|
+
insertAfterId: result.insertAfterId,
|
|
1446
|
+
metaData: options.metaData,
|
|
1311
1447
|
};
|
|
1312
1448
|
|
|
1313
1449
|
updateBatchQueue.push(newUpdate);
|
|
@@ -1324,10 +1460,8 @@ function createEffectiveSetState<T>(
|
|
|
1324
1460
|
if (latestInitialOptionsRef.current?.middleware) {
|
|
1325
1461
|
latestInitialOptionsRef.current.middleware({ update: newUpdate });
|
|
1326
1462
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
syncApiRef.current.updateState({ operation: newUpdate });
|
|
1330
|
-
}
|
|
1463
|
+
runValidation(newUpdate, options.validationTrigger || 'programmatic');
|
|
1464
|
+
notifyUpdate(newUpdate);
|
|
1331
1465
|
}
|
|
1332
1466
|
}
|
|
1333
1467
|
|
|
@@ -1335,22 +1469,18 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1335
1469
|
stateObject: TStateObject,
|
|
1336
1470
|
{
|
|
1337
1471
|
stateKey,
|
|
1338
|
-
|
|
1339
1472
|
localStorage,
|
|
1340
1473
|
formElements,
|
|
1341
1474
|
reactiveDeps,
|
|
1342
1475
|
reactiveType,
|
|
1343
1476
|
componentId,
|
|
1344
1477
|
defaultState,
|
|
1345
|
-
syncUpdate,
|
|
1346
1478
|
dependencies,
|
|
1347
1479
|
serverState,
|
|
1348
|
-
__useSync,
|
|
1349
1480
|
}: {
|
|
1350
1481
|
stateKey?: string;
|
|
1351
1482
|
componentId?: string;
|
|
1352
1483
|
defaultState?: TStateObject;
|
|
1353
|
-
__useSync?: UseSyncType<TStateObject>;
|
|
1354
1484
|
syncOptions?: SyncOptionsType<any>;
|
|
1355
1485
|
} & OptionsType<TStateObject> = {}
|
|
1356
1486
|
) {
|
|
@@ -1365,16 +1495,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1365
1495
|
latestInitialOptionsRef.current = (getInitialOptions(thisKey as string) ??
|
|
1366
1496
|
null) as OptionsType<TStateObject> | null;
|
|
1367
1497
|
|
|
1368
|
-
useEffect(() => {
|
|
1369
|
-
if (syncUpdate && syncUpdate.stateKey === thisKey && syncUpdate.path?.[0]) {
|
|
1370
|
-
const syncKey = `${syncUpdate.stateKey}:${syncUpdate.path.join('.')}`;
|
|
1371
|
-
setSyncInfo(syncKey, {
|
|
1372
|
-
timeStamp: syncUpdate.timeStamp!,
|
|
1373
|
-
userId: syncUpdate.userId!,
|
|
1374
|
-
});
|
|
1375
|
-
}
|
|
1376
|
-
}, [syncUpdate]);
|
|
1377
|
-
|
|
1378
1498
|
const resolveInitialState = useCallback(
|
|
1379
1499
|
(
|
|
1380
1500
|
overrideOptions?: OptionsType<TStateObject>
|
|
@@ -1438,9 +1558,13 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1438
1558
|
|
|
1439
1559
|
// Effect 1: When this component's serverState prop changes, broadcast it
|
|
1440
1560
|
useEffect(() => {
|
|
1441
|
-
|
|
1442
|
-
}, [serverState, thisKey]);
|
|
1561
|
+
if (!serverState) return;
|
|
1443
1562
|
|
|
1563
|
+
// Only broadcast if we have valid server data
|
|
1564
|
+
if (serverState.status === 'success' && serverState.data !== undefined) {
|
|
1565
|
+
setServerStateUpdate(thisKey, serverState);
|
|
1566
|
+
}
|
|
1567
|
+
}, [serverState, thisKey]);
|
|
1444
1568
|
// Effect 2: Listen for server state updates from ANY component
|
|
1445
1569
|
useEffect(() => {
|
|
1446
1570
|
const unsubscribe = getGlobalStore
|
|
@@ -1456,13 +1580,14 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1456
1580
|
return; // Ignore if no valid data
|
|
1457
1581
|
}
|
|
1458
1582
|
|
|
1583
|
+
// Store the server state in options for future reference
|
|
1459
1584
|
setAndMergeOptions(thisKey, { serverState: serverStateData });
|
|
1460
1585
|
|
|
1461
1586
|
const mergeConfig =
|
|
1462
1587
|
typeof serverStateData.merge === 'object'
|
|
1463
1588
|
? serverStateData.merge
|
|
1464
1589
|
: serverStateData.merge === true
|
|
1465
|
-
? { strategy: 'append' }
|
|
1590
|
+
? { strategy: 'append' as const, key: 'id' }
|
|
1466
1591
|
: null;
|
|
1467
1592
|
|
|
1468
1593
|
const currentState = getShadowValue(thisKey, []);
|
|
@@ -1471,7 +1596,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1471
1596
|
if (
|
|
1472
1597
|
mergeConfig &&
|
|
1473
1598
|
mergeConfig.strategy === 'append' &&
|
|
1474
|
-
'key' in mergeConfig &&
|
|
1599
|
+
'key' in mergeConfig &&
|
|
1475
1600
|
Array.isArray(currentState) &&
|
|
1476
1601
|
Array.isArray(incomingData)
|
|
1477
1602
|
) {
|
|
@@ -1483,88 +1608,93 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1483
1608
|
return;
|
|
1484
1609
|
}
|
|
1485
1610
|
|
|
1611
|
+
// Get existing IDs to check for duplicates
|
|
1486
1612
|
const existingIds = new Set(
|
|
1487
1613
|
currentState.map((item: any) => item[keyField])
|
|
1488
1614
|
);
|
|
1489
1615
|
|
|
1616
|
+
// Filter out duplicates from incoming data
|
|
1490
1617
|
const newUniqueItems = incomingData.filter(
|
|
1491
1618
|
(item: any) => !existingIds.has(item[keyField])
|
|
1492
1619
|
);
|
|
1493
1620
|
|
|
1494
1621
|
if (newUniqueItems.length > 0) {
|
|
1622
|
+
// Insert only the new unique items
|
|
1495
1623
|
insertManyShadowArrayElements(thisKey, [], newUniqueItems);
|
|
1496
1624
|
}
|
|
1497
1625
|
|
|
1498
|
-
// Mark the entire
|
|
1626
|
+
// Mark the entire merged state as synced
|
|
1499
1627
|
const finalState = getShadowValue(thisKey, []);
|
|
1500
1628
|
markEntireStateAsServerSynced(
|
|
1501
1629
|
thisKey,
|
|
1502
1630
|
[],
|
|
1503
1631
|
finalState,
|
|
1504
|
-
serverStateData.timestamp
|
|
1632
|
+
serverStateData.timestamp || Date.now()
|
|
1505
1633
|
);
|
|
1506
1634
|
} else {
|
|
1507
|
-
//
|
|
1635
|
+
// Replace strategy (default) - completely replace the state
|
|
1508
1636
|
initializeShadowState(thisKey, incomingData);
|
|
1509
1637
|
|
|
1638
|
+
// Mark as synced from server
|
|
1510
1639
|
markEntireStateAsServerSynced(
|
|
1511
1640
|
thisKey,
|
|
1512
1641
|
[],
|
|
1513
1642
|
incomingData,
|
|
1514
|
-
serverStateData.timestamp
|
|
1643
|
+
serverStateData.timestamp || Date.now()
|
|
1515
1644
|
);
|
|
1516
1645
|
}
|
|
1646
|
+
|
|
1647
|
+
// Notify all components subscribed to this state
|
|
1648
|
+
notifyComponents(thisKey);
|
|
1517
1649
|
}
|
|
1518
1650
|
});
|
|
1519
1651
|
|
|
1520
1652
|
return unsubscribe;
|
|
1521
|
-
}, [thisKey
|
|
1522
|
-
|
|
1653
|
+
}, [thisKey]);
|
|
1523
1654
|
useEffect(() => {
|
|
1524
1655
|
const existingMeta = getGlobalStore
|
|
1525
1656
|
.getState()
|
|
1526
1657
|
.getShadowMetadata(thisKey, []);
|
|
1658
|
+
|
|
1659
|
+
// Skip if already initialized
|
|
1527
1660
|
if (existingMeta && existingMeta.stateSource) {
|
|
1528
|
-
return;
|
|
1661
|
+
return;
|
|
1529
1662
|
}
|
|
1530
1663
|
|
|
1531
1664
|
const options = getInitialOptions(thisKey as string);
|
|
1532
1665
|
|
|
1533
1666
|
const features = {
|
|
1534
|
-
syncEnabled: !!cogsSyncFn && !!syncOpt,
|
|
1535
|
-
validationEnabled: !!(
|
|
1536
|
-
options?.validation?.zodSchemaV4 || options?.validation?.zodSchemaV3
|
|
1537
|
-
),
|
|
1538
1667
|
localStorageEnabled: !!options?.localStorage?.key,
|
|
1539
1668
|
};
|
|
1669
|
+
|
|
1540
1670
|
setShadowMetadata(thisKey, [], {
|
|
1541
1671
|
...existingMeta,
|
|
1542
1672
|
features,
|
|
1543
1673
|
});
|
|
1674
|
+
|
|
1544
1675
|
if (options?.defaultState !== undefined || defaultState !== undefined) {
|
|
1545
1676
|
const finalDefaultState = options?.defaultState || defaultState;
|
|
1546
|
-
|
|
1547
|
-
// Only set defaultState if it's not already set
|
|
1548
1677
|
if (!options?.defaultState) {
|
|
1549
1678
|
setAndMergeOptions(thisKey as string, {
|
|
1550
1679
|
defaultState: finalDefaultState,
|
|
1551
1680
|
});
|
|
1552
1681
|
}
|
|
1682
|
+
}
|
|
1553
1683
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
isDirty: false,
|
|
1563
|
-
baseServerState: source === 'server' ? resolvedState : undefined,
|
|
1564
|
-
});
|
|
1684
|
+
const { value: resolvedState, source, timestamp } = resolveInitialState();
|
|
1685
|
+
initializeShadowState(thisKey, resolvedState);
|
|
1686
|
+
setShadowMetadata(thisKey, [], {
|
|
1687
|
+
stateSource: source,
|
|
1688
|
+
lastServerSync: source === 'server' ? timestamp : undefined,
|
|
1689
|
+
isDirty: source === 'server' ? false : undefined,
|
|
1690
|
+
baseServerState: source === 'server' ? resolvedState : undefined,
|
|
1691
|
+
});
|
|
1565
1692
|
|
|
1566
|
-
|
|
1693
|
+
if (source === 'server' && serverState) {
|
|
1694
|
+
setServerStateUpdate(thisKey, serverState);
|
|
1567
1695
|
}
|
|
1696
|
+
|
|
1697
|
+
notifyComponents(thisKey);
|
|
1568
1698
|
}, [thisKey, ...(dependencies || [])]);
|
|
1569
1699
|
|
|
1570
1700
|
useLayoutEffect(() => {
|
|
@@ -1633,10 +1763,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1633
1763
|
};
|
|
1634
1764
|
}, []);
|
|
1635
1765
|
|
|
1636
|
-
const syncApiRef = useRef<SyncApi | null>(null);
|
|
1637
1766
|
const effectiveSetState = createEffectiveSetState(
|
|
1638
1767
|
thisKey,
|
|
1639
|
-
|
|
1768
|
+
|
|
1640
1769
|
sessionId,
|
|
1641
1770
|
latestInitialOptionsRef
|
|
1642
1771
|
);
|
|
@@ -1656,16 +1785,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1656
1785
|
return handler;
|
|
1657
1786
|
}, [thisKey, sessionId]);
|
|
1658
1787
|
|
|
1659
|
-
const cogsSyncFn = __useSync;
|
|
1660
|
-
const syncOpt = latestInitialOptionsRef.current?.syncOptions;
|
|
1661
|
-
|
|
1662
|
-
if (cogsSyncFn) {
|
|
1663
|
-
syncApiRef.current = cogsSyncFn(
|
|
1664
|
-
updaterFinal as any,
|
|
1665
|
-
syncOpt ?? ({} as any)
|
|
1666
|
-
);
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
1788
|
return updaterFinal;
|
|
1670
1789
|
}
|
|
1671
1790
|
|
|
@@ -1812,7 +1931,6 @@ function createProxyHandler<T>(
|
|
|
1812
1931
|
sessionId?: string
|
|
1813
1932
|
): StateObject<T> {
|
|
1814
1933
|
const proxyCache = new Map<string, any>();
|
|
1815
|
-
let stateVersion = 0;
|
|
1816
1934
|
|
|
1817
1935
|
function rebuildStateShape({
|
|
1818
1936
|
path = [],
|
|
@@ -1826,6 +1944,7 @@ function createProxyHandler<T>(
|
|
|
1826
1944
|
const derivationSignature = meta
|
|
1827
1945
|
? JSON.stringify(meta.arrayViews || meta.transforms)
|
|
1828
1946
|
: '';
|
|
1947
|
+
|
|
1829
1948
|
const cacheKey =
|
|
1830
1949
|
path.join('.') + ':' + componentId + ':' + derivationSignature;
|
|
1831
1950
|
if (proxyCache.has(cacheKey)) {
|
|
@@ -1838,10 +1957,17 @@ function createProxyHandler<T>(
|
|
|
1838
1957
|
|
|
1839
1958
|
const handler = {
|
|
1840
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
|
+
}
|
|
1841
1966
|
if (path.length === 0 && prop in rootLevelMethods) {
|
|
1842
1967
|
return rootLevelMethods[prop as keyof typeof rootLevelMethods];
|
|
1843
1968
|
}
|
|
1844
|
-
|
|
1969
|
+
|
|
1970
|
+
if (typeof prop === 'string' && !prop.startsWith('$')) {
|
|
1845
1971
|
const nextPath = [...path, prop];
|
|
1846
1972
|
return rebuildStateShape({
|
|
1847
1973
|
path: nextPath,
|
|
@@ -1919,15 +2045,11 @@ function createProxyHandler<T>(
|
|
|
1919
2045
|
const getStatusFunc = () => {
|
|
1920
2046
|
// ✅ Use the optimized helper to get all data in one efficient call
|
|
1921
2047
|
const { shadowMeta, value } = getScopedData(stateKey, path, meta);
|
|
1922
|
-
|
|
1923
|
-
// Priority 1: Explicitly dirty items. This is the most important status.
|
|
2048
|
+
console.log('getStatusFunc', path, shadowMeta, value);
|
|
1924
2049
|
if (shadowMeta?.isDirty === true) {
|
|
1925
2050
|
return 'dirty';
|
|
1926
2051
|
}
|
|
1927
2052
|
|
|
1928
|
-
// ✅ Priority 2: Synced items. This condition is now cleaner.
|
|
1929
|
-
// An item is considered synced if it came from the server OR was explicitly
|
|
1930
|
-
// marked as not dirty (isDirty: false), covering all sync-related cases.
|
|
1931
2053
|
if (
|
|
1932
2054
|
shadowMeta?.stateSource === 'server' ||
|
|
1933
2055
|
shadowMeta?.isDirty === false
|
|
@@ -1935,20 +2057,15 @@ function createProxyHandler<T>(
|
|
|
1935
2057
|
return 'synced';
|
|
1936
2058
|
}
|
|
1937
2059
|
|
|
1938
|
-
// Priority 3: Items restored from localStorage.
|
|
1939
2060
|
if (shadowMeta?.stateSource === 'localStorage') {
|
|
1940
2061
|
return 'restored';
|
|
1941
2062
|
}
|
|
1942
2063
|
|
|
1943
|
-
// Priority 4: Items from default/initial state.
|
|
1944
2064
|
if (shadowMeta?.stateSource === 'default') {
|
|
1945
2065
|
return 'fresh';
|
|
1946
2066
|
}
|
|
1947
2067
|
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
// Priority 5: A value exists but has no metadata. This is a fallback.
|
|
1951
|
-
if (value !== undefined && !shadowMeta) {
|
|
2068
|
+
if (value !== undefined) {
|
|
1952
2069
|
return 'fresh';
|
|
1953
2070
|
}
|
|
1954
2071
|
|
|
@@ -2425,7 +2542,7 @@ function createProxyHandler<T>(
|
|
|
2425
2542
|
path,
|
|
2426
2543
|
meta
|
|
2427
2544
|
);
|
|
2428
|
-
|
|
2545
|
+
registerComponentDependency(stateKey, componentId, path);
|
|
2429
2546
|
if (!arrayKeys || !Array.isArray(shadowValue)) {
|
|
2430
2547
|
return []; // It's valid to map over an empty array.
|
|
2431
2548
|
}
|
|
@@ -2776,8 +2893,6 @@ function createProxyHandler<T>(
|
|
|
2776
2893
|
|
|
2777
2894
|
if (!Array.isArray(currentState)) return [];
|
|
2778
2895
|
|
|
2779
|
-
stateVersion++;
|
|
2780
|
-
|
|
2781
2896
|
return rebuildStateShape({
|
|
2782
2897
|
path: [...path, '[*]', fieldName],
|
|
2783
2898
|
componentId: componentId!,
|
|
@@ -2839,7 +2954,18 @@ function createProxyHandler<T>(
|
|
|
2839
2954
|
payload: InsertParams<InferArrayElement<T>>,
|
|
2840
2955
|
index?: number
|
|
2841
2956
|
) => {
|
|
2842
|
-
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
|
+
});
|
|
2843
2969
|
};
|
|
2844
2970
|
}
|
|
2845
2971
|
if (prop === '$uniqueInsert') {
|
|
@@ -2887,6 +3013,7 @@ function createProxyHandler<T>(
|
|
|
2887
3013
|
if (prop === '$cut') {
|
|
2888
3014
|
return (index?: number, options?: { waitForSync?: boolean }) => {
|
|
2889
3015
|
const shadowMeta = getShadowMetadata(stateKey, path);
|
|
3016
|
+
console.log('shadowMeta ->>>>>>>>>>>>>>>>', shadowMeta);
|
|
2890
3017
|
if (!shadowMeta?.arrayKeys || shadowMeta.arrayKeys.length === 0)
|
|
2891
3018
|
return;
|
|
2892
3019
|
|
|
@@ -2897,8 +3024,11 @@ function createProxyHandler<T>(
|
|
|
2897
3024
|
? index
|
|
2898
3025
|
: shadowMeta.arrayKeys.length - 1;
|
|
2899
3026
|
|
|
3027
|
+
console.log('indexToCut ->>>>>>>>>>>>>>>>', indexToCut);
|
|
3028
|
+
|
|
2900
3029
|
const idToCut = shadowMeta.arrayKeys[indexToCut];
|
|
2901
3030
|
if (!idToCut) return;
|
|
3031
|
+
console.log('idToCut ->>>>>>>>>>>>>>>>', idToCut);
|
|
2902
3032
|
|
|
2903
3033
|
effectiveSetState(null, [...path, idToCut], {
|
|
2904
3034
|
updateType: 'cut',
|
|
@@ -3000,11 +3130,7 @@ function createProxyHandler<T>(
|
|
|
3000
3130
|
});
|
|
3001
3131
|
}
|
|
3002
3132
|
|
|
3003
|
-
return
|
|
3004
|
-
path: [...path, `not_found_${uuidv4()}`],
|
|
3005
|
-
componentId: componentId!,
|
|
3006
|
-
meta,
|
|
3007
|
-
});
|
|
3133
|
+
return null;
|
|
3008
3134
|
};
|
|
3009
3135
|
}
|
|
3010
3136
|
if (prop === '$cutThis') {
|
|
@@ -3034,10 +3160,59 @@ function createProxyHandler<T>(
|
|
|
3034
3160
|
});
|
|
3035
3161
|
}
|
|
3036
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
|
+
// }
|
|
3037
3211
|
if (prop === '$$get') {
|
|
3038
3212
|
return () =>
|
|
3039
3213
|
$cogsSignal({ _stateKey: stateKey, _path: path, _meta: meta });
|
|
3040
3214
|
}
|
|
3215
|
+
|
|
3041
3216
|
if (prop === '$lastSynced') {
|
|
3042
3217
|
const syncKey = `${stateKey}:${path.join('.')}`;
|
|
3043
3218
|
return getSyncInfo(syncKey);
|
|
@@ -3109,8 +3284,43 @@ function createProxyHandler<T>(
|
|
|
3109
3284
|
return componentId;
|
|
3110
3285
|
}
|
|
3111
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
|
+
}
|
|
3112
3319
|
if (prop === '$addZodValidation') {
|
|
3113
|
-
return (
|
|
3320
|
+
return (
|
|
3321
|
+
zodErrors: any[],
|
|
3322
|
+
source: 'client' | 'sync_engine' | 'api'
|
|
3323
|
+
) => {
|
|
3114
3324
|
zodErrors.forEach((error) => {
|
|
3115
3325
|
const currentMeta =
|
|
3116
3326
|
getGlobalStore
|
|
@@ -3125,7 +3335,7 @@ function createProxyHandler<T>(
|
|
|
3125
3335
|
status: 'INVALID',
|
|
3126
3336
|
errors: [
|
|
3127
3337
|
{
|
|
3128
|
-
source: 'client',
|
|
3338
|
+
source: source || 'client',
|
|
3129
3339
|
message: error.message,
|
|
3130
3340
|
severity: 'error',
|
|
3131
3341
|
code: error.code,
|
|
@@ -3156,6 +3366,72 @@ function createProxyHandler<T>(
|
|
|
3156
3366
|
});
|
|
3157
3367
|
};
|
|
3158
3368
|
}
|
|
3369
|
+
|
|
3370
|
+
if (prop === '$applyOperation') {
|
|
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',
|
|
3407
|
+
getGlobalStore
|
|
3408
|
+
.getState()
|
|
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
|
+
}
|
|
3426
|
+
|
|
3427
|
+
effectiveSetState(operation.newValue, operation.path, {
|
|
3428
|
+
updateType: operation.updateType,
|
|
3429
|
+
itemId: operation.itemId,
|
|
3430
|
+
index, // Pass the calculated index
|
|
3431
|
+
metaData,
|
|
3432
|
+
});
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3159
3435
|
if (prop === '$applyJsonPatch') {
|
|
3160
3436
|
return (patches: Operation[]) => {
|
|
3161
3437
|
const store = getGlobalStore.getState();
|
|
@@ -3250,14 +3526,8 @@ function createProxyHandler<T>(
|
|
|
3250
3526
|
|
|
3251
3527
|
if (prop === '$getComponents')
|
|
3252
3528
|
return () => getShadowMetadata(stateKey, [])?.components;
|
|
3253
|
-
if (prop === '$getAllFormRefs')
|
|
3254
|
-
return () =>
|
|
3255
|
-
formRefStore.getState().getFormRefsByStateKey(stateKey);
|
|
3256
|
-
}
|
|
3257
|
-
if (prop === '$getFormRef') {
|
|
3258
|
-
return () =>
|
|
3259
|
-
formRefStore.getState().getFormRef(stateKey + '.' + path.join('.'));
|
|
3260
3529
|
}
|
|
3530
|
+
|
|
3261
3531
|
if (prop === '$validationWrapper') {
|
|
3262
3532
|
return ({
|
|
3263
3533
|
children,
|
|
@@ -3277,6 +3547,7 @@ function createProxyHandler<T>(
|
|
|
3277
3547
|
</ValidationWrapper>
|
|
3278
3548
|
);
|
|
3279
3549
|
}
|
|
3550
|
+
|
|
3280
3551
|
if (prop === '$_stateKey') return stateKey;
|
|
3281
3552
|
if (prop === '$_path') return path;
|
|
3282
3553
|
if (prop === '$update') {
|
|
@@ -3289,7 +3560,6 @@ function createProxyHandler<T>(
|
|
|
3289
3560
|
.getState()
|
|
3290
3561
|
.getShadowMetadata(stateKey, path);
|
|
3291
3562
|
|
|
3292
|
-
// Update the metadata for this specific path
|
|
3293
3563
|
setShadowMetadata(stateKey, path, {
|
|
3294
3564
|
...shadowMeta,
|
|
3295
3565
|
isDirty: false,
|
|
@@ -3297,7 +3567,6 @@ function createProxyHandler<T>(
|
|
|
3297
3567
|
lastServerSync: Date.now(),
|
|
3298
3568
|
});
|
|
3299
3569
|
|
|
3300
|
-
// Notify any components that might be subscribed to the sync status
|
|
3301
3570
|
const fullPath = [stateKey, ...path].join('.');
|
|
3302
3571
|
notifyPathSubscribers(fullPath, {
|
|
3303
3572
|
type: 'SYNC_STATUS_CHANGE',
|
|
@@ -3371,21 +3640,15 @@ function createProxyHandler<T>(
|
|
|
3371
3640
|
.getShadowMetadata(stateKey, []);
|
|
3372
3641
|
let revertState;
|
|
3373
3642
|
|
|
3374
|
-
// Determine the correct state to revert to (same logic as before)
|
|
3375
3643
|
if (shadowMeta?.stateSource === 'server' && shadowMeta.baseServerState) {
|
|
3376
3644
|
revertState = shadowMeta.baseServerState;
|
|
3377
3645
|
} else {
|
|
3378
3646
|
revertState = getGlobalStore.getState().initialStateGlobal[stateKey];
|
|
3379
3647
|
}
|
|
3380
3648
|
|
|
3381
|
-
// Perform necessary cleanup
|
|
3382
3649
|
clearSelectedIndexesForState(stateKey);
|
|
3383
|
-
|
|
3384
|
-
// FIX 1: Use the IMMEDIATE, SYNCHRONOUS state reset function.
|
|
3385
|
-
// This is what your tests expect for a clean slate.
|
|
3386
3650
|
initializeShadowState(stateKey, revertState);
|
|
3387
3651
|
|
|
3388
|
-
// Rebuild the proxy's internal shape after the reset
|
|
3389
3652
|
rebuildStateShape({
|
|
3390
3653
|
path: [],
|
|
3391
3654
|
componentId: outerComponentId!,
|
|
@@ -3401,15 +3664,15 @@ function createProxyHandler<T>(
|
|
|
3401
3664
|
localStorage.removeItem(storageKey);
|
|
3402
3665
|
}
|
|
3403
3666
|
|
|
3404
|
-
// FIX 2: Use the library's BATCHED notification system instead of a manual forceUpdate loop.
|
|
3405
|
-
// This fixes the original infinite loop bug safely.
|
|
3406
3667
|
notifyComponents(stateKey);
|
|
3407
3668
|
|
|
3408
3669
|
return revertState;
|
|
3409
3670
|
},
|
|
3671
|
+
$initializeAndMergeShadowState: (newState: T) => {
|
|
3672
|
+
initializeAndMergeShadowState(stateKey, newState);
|
|
3673
|
+
notifyComponents(stateKey);
|
|
3674
|
+
},
|
|
3410
3675
|
$updateInitialState: (newState: T) => {
|
|
3411
|
-
stateVersion++;
|
|
3412
|
-
|
|
3413
3676
|
const newUpdaterState = createProxyHandler(
|
|
3414
3677
|
stateKey,
|
|
3415
3678
|
effectiveSetState,
|
|
@@ -3488,7 +3751,6 @@ function SignalRenderer({
|
|
|
3488
3751
|
|
|
3489
3752
|
const value = getShadowValue(proxy._stateKey, proxy._path, viewIds);
|
|
3490
3753
|
|
|
3491
|
-
// Setup effect - runs only once
|
|
3492
3754
|
useEffect(() => {
|
|
3493
3755
|
const element = elementRef.current;
|
|
3494
3756
|
if (!element || isSetupRef.current) return;
|
|
@@ -3511,7 +3773,6 @@ function SignalRenderer({
|
|
|
3511
3773
|
|
|
3512
3774
|
instanceIdRef.current = `instance-${crypto.randomUUID()}`;
|
|
3513
3775
|
|
|
3514
|
-
// Store signal info in shadow metadata
|
|
3515
3776
|
const currentMeta =
|
|
3516
3777
|
getGlobalStore
|
|
3517
3778
|
.getState()
|