cogsbox-state 0.5.435 → 0.5.437
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CogsState.d.ts +38 -25
- package/dist/CogsState.d.ts.map +1 -0
- package/dist/CogsState.jsx +1292 -1181
- package/dist/CogsState.jsx.map +1 -1
- package/dist/CogsStateClient.d.ts +1 -0
- package/dist/CogsStateClient.d.ts.map +1 -0
- package/dist/CogsStateClient.jsx.map +1 -1
- package/dist/Functions.d.ts +2 -5
- package/dist/Functions.d.ts.map +1 -0
- package/dist/Functions.jsx +17 -48
- package/dist/Functions.jsx.map +1 -1
- package/dist/TRPCValidationLink.d.ts +1 -0
- package/dist/TRPCValidationLink.d.ts.map +1 -0
- package/dist/TRPCValidationLink.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -20
- package/dist/index.js.map +1 -1
- package/dist/store.d.ts +12 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +245 -185
- package/dist/store.js.map +1 -1
- package/dist/utility.d.ts +1 -0
- package/dist/utility.d.ts.map +1 -0
- package/dist/utility.js.map +1 -1
- package/package.json +7 -7
- package/src/CogsState.tsx +500 -179
- package/src/Functions.tsx +8 -241
- package/src/TRPCValidationLink.ts +12 -12
- package/src/index.ts +5 -4
- package/src/store.ts +195 -38
- package/dist/useValidateZodPath.d.ts +0 -34
- package/src/useValidateZodPath.ts +0 -231
package/src/CogsState.tsx
CHANGED
|
@@ -26,12 +26,18 @@ import { ValidationWrapper } from './Functions.js';
|
|
|
26
26
|
import { isDeepEqual, transformStateFunc } from './utility.js';
|
|
27
27
|
import superjson from 'superjson';
|
|
28
28
|
import { v4 as uuidv4 } from 'uuid';
|
|
29
|
-
import { z } from 'zod';
|
|
30
29
|
|
|
31
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
formRefStore,
|
|
32
|
+
getGlobalStore,
|
|
33
|
+
ValidationStatus,
|
|
34
|
+
type ComponentsType,
|
|
35
|
+
} from './store.js';
|
|
32
36
|
import { useCogsConfig } from './CogsStateClient.js';
|
|
33
37
|
import { applyPatch, compare, Operation } from 'fast-json-patch';
|
|
34
38
|
import { useInView } from 'react-intersection-observer';
|
|
39
|
+
import * as z3 from 'zod/v3';
|
|
40
|
+
import * as z4 from 'zod/v4';
|
|
35
41
|
|
|
36
42
|
type Prettify<T> = T extends any ? { [K in keyof T]: T[K] } : never;
|
|
37
43
|
|
|
@@ -217,12 +223,15 @@ export type FormOptsType = {
|
|
|
217
223
|
validation?: {
|
|
218
224
|
hideMessage?: boolean;
|
|
219
225
|
message?: string;
|
|
220
|
-
|
|
226
|
+
|
|
221
227
|
props?: GenericObject;
|
|
222
228
|
disable?: boolean;
|
|
223
229
|
};
|
|
224
230
|
|
|
225
231
|
debounceTime?: number;
|
|
232
|
+
sync?: {
|
|
233
|
+
allowInvalidValues?: boolean; // default: false
|
|
234
|
+
};
|
|
226
235
|
};
|
|
227
236
|
|
|
228
237
|
export type FormControl<T> = (obj: FormElementParams<T>) => JSX.Element;
|
|
@@ -248,6 +257,7 @@ export type EndType<T, IsArrayElement = false> = {
|
|
|
248
257
|
_stateKey: string;
|
|
249
258
|
formElement: (control: FormControl<T>, opts?: FormOptsType) => JSX.Element;
|
|
250
259
|
get: () => T;
|
|
260
|
+
getState: () => T;
|
|
251
261
|
$get: () => T;
|
|
252
262
|
$derive: <R>(fn: EffectFunction<T, R>) => R;
|
|
253
263
|
|
|
@@ -317,13 +327,17 @@ type EffectiveSetStateArg<
|
|
|
317
327
|
? InsertParams<InferArrayElement<T>>
|
|
318
328
|
: never
|
|
319
329
|
: UpdateArg<T>;
|
|
330
|
+
type UpdateOptions = {
|
|
331
|
+
updateType: 'insert' | 'cut' | 'update';
|
|
320
332
|
|
|
333
|
+
sync?: boolean;
|
|
334
|
+
};
|
|
321
335
|
type EffectiveSetState<TStateObject> = (
|
|
322
336
|
newStateOrFunction:
|
|
323
337
|
| EffectiveSetStateArg<TStateObject, 'update'>
|
|
324
338
|
| EffectiveSetStateArg<TStateObject, 'insert'>,
|
|
325
339
|
path: string[],
|
|
326
|
-
updateObj:
|
|
340
|
+
updateObj: UpdateOptions,
|
|
327
341
|
validationKey?: string
|
|
328
342
|
) => void;
|
|
329
343
|
|
|
@@ -346,11 +360,6 @@ export type ReactivityType =
|
|
|
346
360
|
| 'all'
|
|
347
361
|
| Array<Prettify<'none' | 'component' | 'deps' | 'all'>>;
|
|
348
362
|
|
|
349
|
-
type ValidationOptionsType = {
|
|
350
|
-
key?: string;
|
|
351
|
-
zodSchema?: z.ZodTypeAny;
|
|
352
|
-
onBlur?: boolean;
|
|
353
|
-
};
|
|
354
363
|
// Define the return type of the sync hook locally
|
|
355
364
|
type SyncApi = {
|
|
356
365
|
updateState: (data: { operation: any }) => void;
|
|
@@ -358,6 +367,13 @@ type SyncApi = {
|
|
|
358
367
|
clientId: string | null;
|
|
359
368
|
subscribers: string[];
|
|
360
369
|
};
|
|
370
|
+
type ValidationOptionsType = {
|
|
371
|
+
key?: string;
|
|
372
|
+
zodSchemaV3?: z3.ZodType<any, any, any>;
|
|
373
|
+
zodSchemaV4?: z4.ZodType<any, any, any>;
|
|
374
|
+
|
|
375
|
+
onBlur?: boolean;
|
|
376
|
+
};
|
|
361
377
|
export type OptionsType<T extends unknown = unknown> = {
|
|
362
378
|
log?: boolean;
|
|
363
379
|
componentId?: string;
|
|
@@ -402,7 +418,7 @@ export type OptionsType<T extends unknown = unknown> = {
|
|
|
402
418
|
key: string | ((state: T) => string);
|
|
403
419
|
onChange?: (state: T) => void;
|
|
404
420
|
};
|
|
405
|
-
formElements?: FormsElementsType
|
|
421
|
+
formElements?: FormsElementsType<T>;
|
|
406
422
|
|
|
407
423
|
reactiveDeps?: (state: T) => any[] | true;
|
|
408
424
|
reactiveType?: ReactivityType;
|
|
@@ -412,15 +428,6 @@ export type OptionsType<T extends unknown = unknown> = {
|
|
|
412
428
|
dependencies?: any[];
|
|
413
429
|
};
|
|
414
430
|
|
|
415
|
-
export type ValidationWrapperOptions<T extends unknown = unknown> = {
|
|
416
|
-
children: React.ReactNode;
|
|
417
|
-
active: boolean;
|
|
418
|
-
stretch?: boolean;
|
|
419
|
-
path: string[];
|
|
420
|
-
message?: string;
|
|
421
|
-
data?: T;
|
|
422
|
-
key?: string;
|
|
423
|
-
};
|
|
424
431
|
export type SyncRenderOptions<T extends unknown = unknown> = {
|
|
425
432
|
children: React.ReactNode;
|
|
426
433
|
time: number;
|
|
@@ -428,8 +435,16 @@ export type SyncRenderOptions<T extends unknown = unknown> = {
|
|
|
428
435
|
key?: string;
|
|
429
436
|
};
|
|
430
437
|
|
|
431
|
-
type FormsElementsType<T
|
|
432
|
-
validation?: (options:
|
|
438
|
+
type FormsElementsType<T> = {
|
|
439
|
+
validation?: (options: {
|
|
440
|
+
children: React.ReactNode;
|
|
441
|
+
status: ValidationStatus; // Instead of 'active' boolean
|
|
442
|
+
|
|
443
|
+
path: string[];
|
|
444
|
+
message?: string;
|
|
445
|
+
data?: T;
|
|
446
|
+
key?: string;
|
|
447
|
+
}) => React.ReactNode;
|
|
433
448
|
syncRender?: (options: SyncRenderOptions<T>) => React.ReactNode;
|
|
434
449
|
};
|
|
435
450
|
|
|
@@ -518,15 +533,89 @@ export function addStateOptions<T extends unknown>(
|
|
|
518
533
|
) {
|
|
519
534
|
return { initialState: initialState, formElements, validation } as T;
|
|
520
535
|
}
|
|
536
|
+
type UseCogsStateHook<T extends Record<string, any>> = <
|
|
537
|
+
StateKey extends keyof TransformedStateType<T>,
|
|
538
|
+
>(
|
|
539
|
+
stateKey: StateKey,
|
|
540
|
+
options?: Prettify<OptionsType<TransformedStateType<T>[StateKey]>>
|
|
541
|
+
) => StateObject<TransformedStateType<T>[StateKey]>;
|
|
542
|
+
|
|
543
|
+
// Define the type for the options setter using the Transformed state
|
|
544
|
+
type SetCogsOptionsFunc<T extends Record<string, any>> = <
|
|
545
|
+
StateKey extends keyof TransformedStateType<T>,
|
|
546
|
+
>(
|
|
547
|
+
stateKey: StateKey,
|
|
548
|
+
options: OptionsType<TransformedStateType<T>[StateKey]>
|
|
549
|
+
) => void;
|
|
550
|
+
|
|
551
|
+
// Define the final API object shape
|
|
552
|
+
type CogsApi<T extends Record<string, any>> = {
|
|
553
|
+
useCogsState: UseCogsStateHook<T>;
|
|
554
|
+
setCogsOptions: SetCogsOptionsFunc<T>;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
type ExtractStateFromSyncSchema<T> = T extends {
|
|
558
|
+
schemas: infer S;
|
|
559
|
+
notifications: any;
|
|
560
|
+
}
|
|
561
|
+
? S extends Record<string, any>
|
|
562
|
+
? {
|
|
563
|
+
[K in keyof S]: S[K] extends { rawSchema: infer R }
|
|
564
|
+
? R
|
|
565
|
+
: S[K] extends { schemas: { defaults: infer D } }
|
|
566
|
+
? D
|
|
567
|
+
: never;
|
|
568
|
+
}
|
|
569
|
+
: never
|
|
570
|
+
: never;
|
|
571
|
+
|
|
572
|
+
// Type to extract just the sync schema structure
|
|
573
|
+
type SyncSchemaStructure<T = any> = {
|
|
574
|
+
schemas: Record<
|
|
575
|
+
string,
|
|
576
|
+
{
|
|
577
|
+
rawSchema?: any;
|
|
578
|
+
schemas?: {
|
|
579
|
+
sql?: any;
|
|
580
|
+
client?: any;
|
|
581
|
+
validation?: any;
|
|
582
|
+
defaults?: any;
|
|
583
|
+
};
|
|
584
|
+
api?: {
|
|
585
|
+
initialData?: string;
|
|
586
|
+
update?: string;
|
|
587
|
+
};
|
|
588
|
+
validate?: (data: unknown, ctx: any) => any;
|
|
589
|
+
validateClient?: (data: unknown) => any;
|
|
590
|
+
serializable?: any;
|
|
591
|
+
}
|
|
592
|
+
>;
|
|
593
|
+
notifications: Record<string, (state: any, context: any) => any>;
|
|
594
|
+
};
|
|
521
595
|
export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
522
596
|
initialState: State,
|
|
523
|
-
opt?: {
|
|
597
|
+
opt?: {
|
|
598
|
+
formElements?: FormsElementsType<State>;
|
|
599
|
+
validation?: ValidationOptionsType;
|
|
600
|
+
// Add this flag to indicate it's from sync schema
|
|
601
|
+
__fromSyncSchema?: boolean;
|
|
602
|
+
__syncNotifications?: Record<string, Function>;
|
|
603
|
+
}
|
|
524
604
|
) => {
|
|
605
|
+
// Keep ALL your existing code exactly the same
|
|
525
606
|
let newInitialState = initialState;
|
|
526
607
|
|
|
527
608
|
const [statePart, initialOptionsPart] =
|
|
528
609
|
transformStateFunc<State>(newInitialState);
|
|
529
610
|
|
|
611
|
+
// Only addition - store notifications if provided
|
|
612
|
+
if (opt?.__fromSyncSchema && opt?.__syncNotifications) {
|
|
613
|
+
getGlobalStore
|
|
614
|
+
.getState()
|
|
615
|
+
.setInitialStateOptions('__notifications', opt.__syncNotifications);
|
|
616
|
+
}
|
|
617
|
+
// ... rest of your existing createCogsState code unchanged ...
|
|
618
|
+
|
|
530
619
|
Object.keys(statePart).forEach((key) => {
|
|
531
620
|
let existingOptions = initialOptionsPart[key] || {};
|
|
532
621
|
|
|
@@ -575,12 +664,13 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
575
664
|
|
|
576
665
|
const useCogsState = <StateKey extends StateKeys>(
|
|
577
666
|
stateKey: StateKey,
|
|
578
|
-
options?: OptionsType<(typeof statePart)[StateKey]
|
|
667
|
+
options?: Prettify<OptionsType<(typeof statePart)[StateKey]>>
|
|
579
668
|
) => {
|
|
669
|
+
// ... your existing useCogsState implementation ...
|
|
580
670
|
const [componentId] = useState(options?.componentId ?? uuidv4());
|
|
581
671
|
setOptions({
|
|
582
672
|
stateKey,
|
|
583
|
-
options,
|
|
673
|
+
options: options as any,
|
|
584
674
|
initialOptionsPart,
|
|
585
675
|
});
|
|
586
676
|
|
|
@@ -597,7 +687,6 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
597
687
|
componentId,
|
|
598
688
|
localStorage: options?.localStorage,
|
|
599
689
|
middleware: options?.middleware,
|
|
600
|
-
|
|
601
690
|
reactiveType: options?.reactiveType,
|
|
602
691
|
reactiveDeps: options?.reactiveDeps,
|
|
603
692
|
defaultState: options?.defaultState as any,
|
|
@@ -621,9 +710,36 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
621
710
|
notifyComponents(stateKey as string);
|
|
622
711
|
}
|
|
623
712
|
|
|
624
|
-
return { useCogsState, setCogsOptions }
|
|
713
|
+
return { useCogsState, setCogsOptions } as CogsApi<State>;
|
|
625
714
|
};
|
|
626
715
|
|
|
716
|
+
// Then create a simple helper that extracts state from sync schema
|
|
717
|
+
export function createCogsStateFromSync<
|
|
718
|
+
T extends Record<string, any>,
|
|
719
|
+
>(syncSchema: {
|
|
720
|
+
schemas: any;
|
|
721
|
+
notifications: any;
|
|
722
|
+
}): ReturnType<typeof createCogsState<T>> {
|
|
723
|
+
// Extract initial state
|
|
724
|
+
const initialState = {} as T;
|
|
725
|
+
|
|
726
|
+
for (const key in syncSchema.schemas) {
|
|
727
|
+
const entry = syncSchema.schemas[key];
|
|
728
|
+
if (entry.rawSchema) {
|
|
729
|
+
initialState[key as keyof T] = entry.rawSchema;
|
|
730
|
+
} else if (entry.schemas?.defaults) {
|
|
731
|
+
initialState[key as keyof T] = entry.schemas.defaults;
|
|
732
|
+
} else {
|
|
733
|
+
initialState[key as keyof T] = {} as any;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return createCogsState(initialState, {
|
|
738
|
+
__fromSyncSchema: true,
|
|
739
|
+
__syncNotifications: syncSchema.notifications,
|
|
740
|
+
}) as any;
|
|
741
|
+
}
|
|
742
|
+
|
|
627
743
|
const {
|
|
628
744
|
getInitialOptions,
|
|
629
745
|
getValidationErrors,
|
|
@@ -1241,9 +1357,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1241
1357
|
...rootMeta,
|
|
1242
1358
|
components,
|
|
1243
1359
|
});
|
|
1244
|
-
|
|
1245
1360
|
forceUpdate({});
|
|
1246
|
-
|
|
1247
1361
|
return () => {
|
|
1248
1362
|
const meta = getGlobalStore.getState().getShadowMetadata(thisKey, []);
|
|
1249
1363
|
const component = meta?.components?.get(componentKey);
|
|
@@ -1282,8 +1396,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1282
1396
|
const effectiveSetState = (
|
|
1283
1397
|
newStateOrFunction: UpdateArg<TStateObject> | InsertParams<TStateObject>,
|
|
1284
1398
|
path: string[],
|
|
1285
|
-
updateObj:
|
|
1286
|
-
validationKey?: string
|
|
1399
|
+
updateObj: UpdateOptions
|
|
1287
1400
|
) => {
|
|
1288
1401
|
const fullPath = [thisKey, ...path].join('.');
|
|
1289
1402
|
if (Array.isArray(path)) {
|
|
@@ -1349,9 +1462,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1349
1462
|
break;
|
|
1350
1463
|
}
|
|
1351
1464
|
}
|
|
1465
|
+
const shouldSync = updateObj.sync !== false;
|
|
1352
1466
|
|
|
1353
|
-
|
|
1354
|
-
if (syncApiRef.current && syncApiRef.current.connected) {
|
|
1467
|
+
if (shouldSync && syncApiRef.current && syncApiRef.current.connected) {
|
|
1355
1468
|
syncApiRef.current.updateState({ operation: newUpdate });
|
|
1356
1469
|
}
|
|
1357
1470
|
// Handle signals - reuse shadowMeta from the beginning
|
|
@@ -1519,59 +1632,18 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1519
1632
|
});
|
|
1520
1633
|
}
|
|
1521
1634
|
}
|
|
1522
|
-
if (
|
|
1523
|
-
updateObj.updateType === 'update' &&
|
|
1524
|
-
(validationKey || latestInitialOptionsRef.current?.validation?.key) &&
|
|
1525
|
-
path
|
|
1526
|
-
) {
|
|
1527
|
-
removeValidationError(
|
|
1528
|
-
(validationKey || latestInitialOptionsRef.current?.validation?.key) +
|
|
1529
|
-
'.' +
|
|
1530
|
-
path.join('.')
|
|
1531
|
-
);
|
|
1532
|
-
}
|
|
1533
|
-
const arrayWithoutIndex = path.slice(0, path.length - 1);
|
|
1534
|
-
if (
|
|
1535
|
-
updateObj.updateType === 'cut' &&
|
|
1536
|
-
latestInitialOptionsRef.current?.validation?.key
|
|
1537
|
-
) {
|
|
1538
|
-
removeValidationError(
|
|
1539
|
-
latestInitialOptionsRef.current?.validation?.key +
|
|
1540
|
-
'.' +
|
|
1541
|
-
arrayWithoutIndex.join('.')
|
|
1542
|
-
);
|
|
1543
|
-
}
|
|
1544
|
-
if (
|
|
1545
|
-
updateObj.updateType === 'insert' &&
|
|
1546
|
-
latestInitialOptionsRef.current?.validation?.key
|
|
1547
|
-
) {
|
|
1548
|
-
const getValidation = getValidationErrors(
|
|
1549
|
-
latestInitialOptionsRef.current?.validation?.key +
|
|
1550
|
-
'.' +
|
|
1551
|
-
arrayWithoutIndex.join('.')
|
|
1552
|
-
);
|
|
1553
1635
|
|
|
1554
|
-
getValidation.filter((k) => {
|
|
1555
|
-
let length = k?.split('.').length;
|
|
1556
|
-
const v = ''; // Placeholder as `v` is not used from getValidationErrors
|
|
1557
|
-
|
|
1558
|
-
if (
|
|
1559
|
-
k == arrayWithoutIndex.join('.') &&
|
|
1560
|
-
length == arrayWithoutIndex.length - 1
|
|
1561
|
-
) {
|
|
1562
|
-
let newKey = k + '.' + arrayWithoutIndex;
|
|
1563
|
-
removeValidationError(k!);
|
|
1564
|
-
addValidationError(newKey, v!);
|
|
1565
|
-
}
|
|
1566
|
-
});
|
|
1567
|
-
}
|
|
1568
1636
|
// Assumes `isDeepEqual` is available in this scope.
|
|
1569
1637
|
// Assumes `isDeepEqual` is available in this scope.
|
|
1570
1638
|
|
|
1571
|
-
const newState =
|
|
1572
|
-
const rootMeta =
|
|
1639
|
+
const newState = getGlobalStore.getState().getShadowValue(thisKey);
|
|
1640
|
+
const rootMeta = getGlobalStore.getState().getShadowMetadata(thisKey, []);
|
|
1573
1641
|
const notifiedComponents = new Set<string>();
|
|
1574
|
-
|
|
1642
|
+
console.log(
|
|
1643
|
+
'rootMeta',
|
|
1644
|
+
thisKey,
|
|
1645
|
+
getGlobalStore.getState().shadowStateStore
|
|
1646
|
+
);
|
|
1575
1647
|
if (!rootMeta?.components) {
|
|
1576
1648
|
return newState;
|
|
1577
1649
|
}
|
|
@@ -1579,28 +1651,39 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1579
1651
|
// --- PASS 1: Notify specific subscribers based on update type ---
|
|
1580
1652
|
|
|
1581
1653
|
if (updateObj.updateType === 'update') {
|
|
1582
|
-
//
|
|
1583
|
-
//
|
|
1584
|
-
//
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1654
|
+
// --- Bubble-up Notification ---
|
|
1655
|
+
// When a nested property changes, notify components listening at that exact path,
|
|
1656
|
+
// and also "bubble up" to notify components listening on parent paths.
|
|
1657
|
+
// e.g., an update to `user.address.street` notifies listeners of `street`, `address`, and `user`.
|
|
1658
|
+
let currentPath = [...path]; // Create a mutable copy of the path
|
|
1659
|
+
|
|
1660
|
+
while (true) {
|
|
1661
|
+
const currentPathMeta = store.getShadowMetadata(thisKey, currentPath);
|
|
1662
|
+
|
|
1663
|
+
if (currentPathMeta?.pathComponents) {
|
|
1664
|
+
currentPathMeta.pathComponents.forEach((componentId) => {
|
|
1665
|
+
if (notifiedComponents.has(componentId)) {
|
|
1666
|
+
return; // Avoid sending redundant notifications
|
|
1667
|
+
}
|
|
1668
|
+
const component = rootMeta.components?.get(componentId);
|
|
1669
|
+
if (component) {
|
|
1670
|
+
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1671
|
+
? component.reactiveType
|
|
1672
|
+
: [component.reactiveType || 'component'];
|
|
1596
1673
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1674
|
+
// This notification logic applies to components that depend on object structures.
|
|
1675
|
+
if (!reactiveTypes.includes('none')) {
|
|
1676
|
+
component.forceUpdate();
|
|
1677
|
+
notifiedComponents.add(componentId);
|
|
1678
|
+
}
|
|
1601
1679
|
}
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
if (currentPath.length === 0) {
|
|
1684
|
+
break; // We've reached the root, stop bubbling.
|
|
1685
|
+
}
|
|
1686
|
+
currentPath.pop(); // Go up one level for the next iteration.
|
|
1604
1687
|
}
|
|
1605
1688
|
|
|
1606
1689
|
// ADDITIONALLY, if the payload is an object, perform a deep-check and
|
|
@@ -1880,12 +1963,16 @@ const registerComponentDependency = (
|
|
|
1880
1963
|
dependencyPath: string[]
|
|
1881
1964
|
) => {
|
|
1882
1965
|
const fullComponentId = `${stateKey}////${componentId}`;
|
|
1883
|
-
const
|
|
1966
|
+
const { addPathComponent, getShadowMetadata } = getGlobalStore.getState();
|
|
1967
|
+
|
|
1968
|
+
// First, check if the component should even be registered.
|
|
1969
|
+
// This check is safe to do outside the setter.
|
|
1970
|
+
const rootMeta = getShadowMetadata(stateKey, []);
|
|
1884
1971
|
const component = rootMeta?.components?.get(fullComponentId);
|
|
1885
1972
|
|
|
1886
1973
|
if (
|
|
1887
1974
|
!component ||
|
|
1888
|
-
component.reactiveType
|
|
1975
|
+
component.reactiveType === 'none' ||
|
|
1889
1976
|
!(
|
|
1890
1977
|
Array.isArray(component.reactiveType)
|
|
1891
1978
|
? component.reactiveType
|
|
@@ -1895,23 +1982,9 @@ const registerComponentDependency = (
|
|
|
1895
1982
|
return;
|
|
1896
1983
|
}
|
|
1897
1984
|
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
// Add to component's paths (existing logic)
|
|
1901
|
-
component.paths.add(pathKey);
|
|
1902
|
-
|
|
1903
|
-
// NEW: Also store componentId at the path level
|
|
1904
|
-
const pathMeta =
|
|
1905
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, dependencyPath) || {};
|
|
1906
|
-
const pathComponents = pathMeta.pathComponents || new Set<string>();
|
|
1907
|
-
pathComponents.add(fullComponentId);
|
|
1908
|
-
|
|
1909
|
-
getGlobalStore.getState().setShadowMetadata(stateKey, dependencyPath, {
|
|
1910
|
-
...pathMeta,
|
|
1911
|
-
pathComponents,
|
|
1912
|
-
});
|
|
1985
|
+
// Now, call the single, safe, atomic function to perform the update.
|
|
1986
|
+
addPathComponent(stateKey, dependencyPath, fullComponentId);
|
|
1913
1987
|
};
|
|
1914
|
-
|
|
1915
1988
|
const notifySelectionComponents = (
|
|
1916
1989
|
stateKey: string,
|
|
1917
1990
|
parentPath: string[],
|
|
@@ -2184,13 +2257,16 @@ function createProxyHandler<T>(
|
|
|
2184
2257
|
}
|
|
2185
2258
|
if (prop === 'showValidationErrors') {
|
|
2186
2259
|
return () => {
|
|
2187
|
-
const
|
|
2188
|
-
.getState()
|
|
2189
|
-
.getInitialOptions(stateKey)?.validation;
|
|
2190
|
-
if (!init?.key) throw new Error('Validation key not found');
|
|
2191
|
-
return getGlobalStore
|
|
2260
|
+
const meta = getGlobalStore
|
|
2192
2261
|
.getState()
|
|
2193
|
-
.
|
|
2262
|
+
.getShadowMetadata(stateKey, path);
|
|
2263
|
+
if (
|
|
2264
|
+
meta?.validation?.status === 'VALIDATION_FAILED' &&
|
|
2265
|
+
meta.validation.message
|
|
2266
|
+
) {
|
|
2267
|
+
return [meta.validation.message];
|
|
2268
|
+
}
|
|
2269
|
+
return [];
|
|
2194
2270
|
};
|
|
2195
2271
|
}
|
|
2196
2272
|
if (Array.isArray(currentState)) {
|
|
@@ -2988,7 +3064,7 @@ function createProxyHandler<T>(
|
|
|
2988
3064
|
arrayValues: freshValues || [],
|
|
2989
3065
|
};
|
|
2990
3066
|
}, [cacheKey, updateTrigger]);
|
|
2991
|
-
|
|
3067
|
+
|
|
2992
3068
|
useEffect(() => {
|
|
2993
3069
|
const unsubscribe = getGlobalStore
|
|
2994
3070
|
.getState()
|
|
@@ -3019,7 +3095,6 @@ function createProxyHandler<T>(
|
|
|
3019
3095
|
e.type === 'REMOVE' ||
|
|
3020
3096
|
e.type === 'CLEAR_SELECTION'
|
|
3021
3097
|
) {
|
|
3022
|
-
console.log('sssssssssssssssssssssssssssss', e);
|
|
3023
3098
|
forceUpdate({});
|
|
3024
3099
|
}
|
|
3025
3100
|
});
|
|
@@ -3044,7 +3119,7 @@ function createProxyHandler<T>(
|
|
|
3044
3119
|
validIds: validIds,
|
|
3045
3120
|
},
|
|
3046
3121
|
});
|
|
3047
|
-
|
|
3122
|
+
|
|
3048
3123
|
return (
|
|
3049
3124
|
<>
|
|
3050
3125
|
{arrayValues.map((item, localIndex) => {
|
|
@@ -3221,15 +3296,12 @@ function createProxyHandler<T>(
|
|
|
3221
3296
|
}
|
|
3222
3297
|
if (prop === 'cutSelected') {
|
|
3223
3298
|
return () => {
|
|
3224
|
-
const baseArrayKeys =
|
|
3225
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
3226
|
-
?.arrayKeys || [];
|
|
3227
3299
|
const validKeys = applyTransforms(
|
|
3228
3300
|
stateKey,
|
|
3229
3301
|
path,
|
|
3230
3302
|
meta?.transforms
|
|
3231
3303
|
);
|
|
3232
|
-
|
|
3304
|
+
|
|
3233
3305
|
if (!validKeys || validKeys.length === 0) return;
|
|
3234
3306
|
|
|
3235
3307
|
const indexKeyToCut = getGlobalStore
|
|
@@ -3239,13 +3311,17 @@ function createProxyHandler<T>(
|
|
|
3239
3311
|
let indexToCut = validKeys.findIndex(
|
|
3240
3312
|
(key) => key === indexKeyToCut
|
|
3241
3313
|
);
|
|
3242
|
-
|
|
3314
|
+
|
|
3243
3315
|
const pathForCut = validKeys[
|
|
3244
3316
|
indexToCut == -1 ? validKeys.length - 1 : indexToCut
|
|
3245
3317
|
]
|
|
3246
3318
|
?.split('.')
|
|
3247
3319
|
.slice(1);
|
|
3248
|
-
|
|
3320
|
+
getGlobalStore
|
|
3321
|
+
.getState()
|
|
3322
|
+
.clearSelectedIndex({ arrayKey: stateKeyPathKey });
|
|
3323
|
+
const parentPath = pathForCut?.slice(0, -1)!;
|
|
3324
|
+
notifySelectionComponents(stateKey, parentPath);
|
|
3249
3325
|
effectiveSetState(currentState, pathForCut!, {
|
|
3250
3326
|
updateType: 'cut',
|
|
3251
3327
|
});
|
|
@@ -3371,6 +3447,14 @@ function createProxyHandler<T>(
|
|
|
3371
3447
|
.getShadowValue(stateKeyPathKey, meta?.validIds);
|
|
3372
3448
|
};
|
|
3373
3449
|
}
|
|
3450
|
+
if (prop === 'getState') {
|
|
3451
|
+
return () => {
|
|
3452
|
+
return getGlobalStore
|
|
3453
|
+
.getState()
|
|
3454
|
+
.getShadowValue(stateKeyPathKey, meta?.validIds);
|
|
3455
|
+
};
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3374
3458
|
if (prop === '$derive') {
|
|
3375
3459
|
return (fn: any) =>
|
|
3376
3460
|
$cogsSignal({
|
|
@@ -3481,8 +3565,9 @@ function createProxyHandler<T>(
|
|
|
3481
3565
|
}
|
|
3482
3566
|
if (prop === 'applyJsonPatch') {
|
|
3483
3567
|
return (patches: Operation[]) => {
|
|
3484
|
-
// 1. Get the current state object that the proxy points to.
|
|
3485
3568
|
const store = getGlobalStore.getState();
|
|
3569
|
+
const rootMeta = store.getShadowMetadata(stateKey, []);
|
|
3570
|
+
if (!rootMeta?.components) return;
|
|
3486
3571
|
|
|
3487
3572
|
const convertPath = (jsonPath: string): string[] => {
|
|
3488
3573
|
if (!jsonPath || jsonPath === '/') return [];
|
|
@@ -3492,6 +3577,8 @@ function createProxyHandler<T>(
|
|
|
3492
3577
|
.map((p) => p.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
3493
3578
|
};
|
|
3494
3579
|
|
|
3580
|
+
const notifiedComponents = new Set<string>();
|
|
3581
|
+
|
|
3495
3582
|
for (const patch of patches) {
|
|
3496
3583
|
const relativePath = convertPath(patch.path);
|
|
3497
3584
|
|
|
@@ -3504,18 +3591,64 @@ function createProxyHandler<T>(
|
|
|
3504
3591
|
value: any;
|
|
3505
3592
|
};
|
|
3506
3593
|
store.updateShadowAtPath(stateKey, relativePath, value);
|
|
3507
|
-
store.markAsDirty(stateKey, relativePath, { bubble: true });
|
|
3594
|
+
store.markAsDirty(stateKey, relativePath, { bubble: true });
|
|
3595
|
+
|
|
3596
|
+
// Bubble up - notify components at this path and all parent paths
|
|
3597
|
+
let currentPath = [...relativePath];
|
|
3598
|
+
while (true) {
|
|
3599
|
+
const pathMeta = store.getShadowMetadata(
|
|
3600
|
+
stateKey,
|
|
3601
|
+
currentPath
|
|
3602
|
+
);
|
|
3603
|
+
console.log('pathMeta', pathMeta);
|
|
3604
|
+
if (pathMeta?.pathComponents) {
|
|
3605
|
+
pathMeta.pathComponents.forEach((componentId) => {
|
|
3606
|
+
if (!notifiedComponents.has(componentId)) {
|
|
3607
|
+
const component =
|
|
3608
|
+
rootMeta.components?.get(componentId);
|
|
3609
|
+
if (component) {
|
|
3610
|
+
component.forceUpdate();
|
|
3611
|
+
notifiedComponents.add(componentId);
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
});
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
if (currentPath.length === 0) break;
|
|
3618
|
+
currentPath.pop(); // Go up one level
|
|
3619
|
+
}
|
|
3508
3620
|
break;
|
|
3509
3621
|
}
|
|
3510
3622
|
case 'remove': {
|
|
3511
|
-
store.removeShadowArrayElement(stateKey, relativePath);
|
|
3512
3623
|
const parentPath = relativePath.slice(0, -1);
|
|
3624
|
+
store.removeShadowArrayElement(stateKey, relativePath);
|
|
3513
3625
|
store.markAsDirty(stateKey, parentPath, { bubble: true });
|
|
3626
|
+
|
|
3627
|
+
// Bubble up from parent path
|
|
3628
|
+
let currentPath = [...parentPath];
|
|
3629
|
+
while (true) {
|
|
3630
|
+
const pathMeta = store.getShadowMetadata(
|
|
3631
|
+
stateKey,
|
|
3632
|
+
currentPath
|
|
3633
|
+
);
|
|
3634
|
+
if (pathMeta?.pathComponents) {
|
|
3635
|
+
pathMeta.pathComponents.forEach((componentId) => {
|
|
3636
|
+
if (!notifiedComponents.has(componentId)) {
|
|
3637
|
+
const component =
|
|
3638
|
+
rootMeta.components?.get(componentId);
|
|
3639
|
+
if (component) {
|
|
3640
|
+
component.forceUpdate();
|
|
3641
|
+
notifiedComponents.add(componentId);
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
if (currentPath.length === 0) break;
|
|
3648
|
+
currentPath.pop();
|
|
3649
|
+
}
|
|
3514
3650
|
break;
|
|
3515
3651
|
}
|
|
3516
|
-
// NOTE: 'move' and 'copy' operations should be deconstructed into 'remove' and 'add'
|
|
3517
|
-
// by the server before broadcasting for maximum compatibility. Your server's use
|
|
3518
|
-
// of `compare()` already does this, so we don't need to handle them here.
|
|
3519
3652
|
}
|
|
3520
3653
|
}
|
|
3521
3654
|
};
|
|
@@ -3525,20 +3658,40 @@ function createProxyHandler<T>(
|
|
|
3525
3658
|
const init = getGlobalStore
|
|
3526
3659
|
.getState()
|
|
3527
3660
|
.getInitialOptions(stateKey)?.validation;
|
|
3528
|
-
|
|
3529
|
-
|
|
3661
|
+
|
|
3662
|
+
// UPDATED: Select v4 schema, with a fallback to v3
|
|
3663
|
+
const zodSchema = init?.zodSchemaV4 || init?.zodSchemaV3;
|
|
3664
|
+
|
|
3665
|
+
if (!zodSchema || !init?.key) {
|
|
3666
|
+
throw new Error(
|
|
3667
|
+
'Zod schema (v3 or v4) or validation key not found'
|
|
3668
|
+
);
|
|
3669
|
+
}
|
|
3530
3670
|
|
|
3531
3671
|
removeValidationError(init.key);
|
|
3532
3672
|
const thisObject = getGlobalStore
|
|
3533
3673
|
.getState()
|
|
3534
3674
|
.getShadowValue(stateKey);
|
|
3535
|
-
|
|
3675
|
+
|
|
3676
|
+
// Use the selected schema for parsing
|
|
3677
|
+
const result = zodSchema.safeParse(thisObject);
|
|
3536
3678
|
|
|
3537
3679
|
if (!result.success) {
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3680
|
+
// This logic already handles both v3 and v4 error types correctly
|
|
3681
|
+
if ('issues' in result.error) {
|
|
3682
|
+
// Zod v4 error
|
|
3683
|
+
result.error.issues.forEach((error) => {
|
|
3684
|
+
const fullErrorPath = [init.key, ...error.path].join('.');
|
|
3685
|
+
addValidationError(fullErrorPath, error.message);
|
|
3686
|
+
});
|
|
3687
|
+
} else {
|
|
3688
|
+
// Zod v3 error
|
|
3689
|
+
(result.error as any).errors.forEach((error: any) => {
|
|
3690
|
+
const fullErrorPath = [init.key, ...error.path].join('.');
|
|
3691
|
+
addValidationError(fullErrorPath, error.message);
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3542
3695
|
notifyComponents(stateKey);
|
|
3543
3696
|
return false;
|
|
3544
3697
|
}
|
|
@@ -3631,20 +3784,14 @@ function createProxyHandler<T>(
|
|
|
3631
3784
|
if (prop === 'formElement') {
|
|
3632
3785
|
return (child: FormControl<T>, formOpts?: FormOptsType) => {
|
|
3633
3786
|
return (
|
|
3634
|
-
<
|
|
3635
|
-
formOpts={formOpts}
|
|
3636
|
-
path={path}
|
|
3787
|
+
<FormElementWrapper
|
|
3637
3788
|
stateKey={stateKey}
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
formOpts={formOpts}
|
|
3645
|
-
renderFn={child as any}
|
|
3646
|
-
/>
|
|
3647
|
-
</ValidationWrapper>
|
|
3789
|
+
path={path}
|
|
3790
|
+
rebuildStateShape={rebuildStateShape}
|
|
3791
|
+
setState={effectiveSetState}
|
|
3792
|
+
formOpts={formOpts}
|
|
3793
|
+
renderFn={child as any}
|
|
3794
|
+
/>
|
|
3648
3795
|
);
|
|
3649
3796
|
};
|
|
3650
3797
|
}
|
|
@@ -4203,6 +4350,7 @@ function ListItemWrapper({
|
|
|
4203
4350
|
|
|
4204
4351
|
return <div ref={setRefs}>{children}</div>;
|
|
4205
4352
|
}
|
|
4353
|
+
|
|
4206
4354
|
function FormElementWrapper({
|
|
4207
4355
|
stateKey,
|
|
4208
4356
|
path,
|
|
@@ -4248,7 +4396,9 @@ function FormElementWrapper({
|
|
|
4248
4396
|
const unsubscribe = getGlobalStore
|
|
4249
4397
|
.getState()
|
|
4250
4398
|
.subscribeToPath(stateKeyPathKey, (newValue) => {
|
|
4251
|
-
|
|
4399
|
+
if (!isCurrentlyDebouncing.current && localValue !== newValue) {
|
|
4400
|
+
forceUpdate({});
|
|
4401
|
+
}
|
|
4252
4402
|
});
|
|
4253
4403
|
return () => {
|
|
4254
4404
|
unsubscribe();
|
|
@@ -4261,6 +4411,10 @@ function FormElementWrapper({
|
|
|
4261
4411
|
|
|
4262
4412
|
const debouncedUpdate = useCallback(
|
|
4263
4413
|
(newValue: any) => {
|
|
4414
|
+
const currentType = typeof globalStateValue;
|
|
4415
|
+
if (currentType === 'number' && typeof newValue === 'string') {
|
|
4416
|
+
newValue = newValue === '' ? 0 : Number(newValue);
|
|
4417
|
+
}
|
|
4264
4418
|
setLocalValue(newValue);
|
|
4265
4419
|
isCurrentlyDebouncing.current = true;
|
|
4266
4420
|
|
|
@@ -4272,19 +4426,188 @@ function FormElementWrapper({
|
|
|
4272
4426
|
|
|
4273
4427
|
debounceTimeoutRef.current = setTimeout(() => {
|
|
4274
4428
|
isCurrentlyDebouncing.current = false;
|
|
4429
|
+
|
|
4430
|
+
// Update state
|
|
4275
4431
|
setState(newValue, path, { updateType: 'update' });
|
|
4432
|
+
|
|
4433
|
+
// Perform LIVE validation (gentle)
|
|
4434
|
+
const { getInitialOptions, setShadowMetadata, getShadowMetadata } =
|
|
4435
|
+
getGlobalStore.getState();
|
|
4436
|
+
const validationOptions = getInitialOptions(stateKey)?.validation;
|
|
4437
|
+
const zodSchema =
|
|
4438
|
+
validationOptions?.zodSchemaV4 || validationOptions?.zodSchemaV3;
|
|
4439
|
+
|
|
4440
|
+
if (zodSchema) {
|
|
4441
|
+
const fullState = getGlobalStore.getState().getShadowValue(stateKey);
|
|
4442
|
+
const result = zodSchema.safeParse(fullState);
|
|
4443
|
+
|
|
4444
|
+
const currentMeta = getShadowMetadata(stateKey, path) || {};
|
|
4445
|
+
|
|
4446
|
+
if (!result.success) {
|
|
4447
|
+
const errors =
|
|
4448
|
+
'issues' in result.error
|
|
4449
|
+
? result.error.issues
|
|
4450
|
+
: (result.error as any).errors;
|
|
4451
|
+
const pathErrors = errors.filter(
|
|
4452
|
+
(error: any) =>
|
|
4453
|
+
JSON.stringify(error.path) === JSON.stringify(path)
|
|
4454
|
+
);
|
|
4455
|
+
|
|
4456
|
+
if (pathErrors.length > 0) {
|
|
4457
|
+
setShadowMetadata(stateKey, path, {
|
|
4458
|
+
...currentMeta,
|
|
4459
|
+
validation: {
|
|
4460
|
+
status: 'INVALID_LIVE',
|
|
4461
|
+
message: pathErrors[0]?.message,
|
|
4462
|
+
validatedValue: newValue,
|
|
4463
|
+
},
|
|
4464
|
+
});
|
|
4465
|
+
} else {
|
|
4466
|
+
// This field has no errors - clear validation
|
|
4467
|
+
setShadowMetadata(stateKey, path, {
|
|
4468
|
+
...currentMeta,
|
|
4469
|
+
validation: {
|
|
4470
|
+
status: 'VALID_LIVE',
|
|
4471
|
+
validatedValue: newValue,
|
|
4472
|
+
},
|
|
4473
|
+
});
|
|
4474
|
+
}
|
|
4475
|
+
} else {
|
|
4476
|
+
// Validation passed - clear any existing errors
|
|
4477
|
+
setShadowMetadata(stateKey, path, {
|
|
4478
|
+
...currentMeta,
|
|
4479
|
+
validation: {
|
|
4480
|
+
status: 'VALID_LIVE',
|
|
4481
|
+
validatedValue: newValue,
|
|
4482
|
+
},
|
|
4483
|
+
});
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4276
4486
|
}, debounceTime);
|
|
4487
|
+
forceUpdate({});
|
|
4277
4488
|
},
|
|
4278
|
-
[setState, path, formOpts?.debounceTime]
|
|
4489
|
+
[setState, path, formOpts?.debounceTime, stateKey]
|
|
4279
4490
|
);
|
|
4280
4491
|
|
|
4281
|
-
|
|
4492
|
+
// --- NEW onBlur HANDLER ---
|
|
4493
|
+
// This replaces the old commented-out method with a modern approach.
|
|
4494
|
+
const handleBlur = useCallback(async () => {
|
|
4495
|
+
console.log('handleBlur triggered');
|
|
4496
|
+
|
|
4497
|
+
// Commit any pending changes
|
|
4282
4498
|
if (debounceTimeoutRef.current) {
|
|
4283
4499
|
clearTimeout(debounceTimeoutRef.current);
|
|
4500
|
+
debounceTimeoutRef.current = null;
|
|
4284
4501
|
isCurrentlyDebouncing.current = false;
|
|
4285
4502
|
setState(localValue, path, { updateType: 'update' });
|
|
4286
4503
|
}
|
|
4287
|
-
|
|
4504
|
+
|
|
4505
|
+
const { getInitialOptions } = getGlobalStore.getState();
|
|
4506
|
+
const validationOptions = getInitialOptions(stateKey)?.validation;
|
|
4507
|
+
const zodSchema =
|
|
4508
|
+
validationOptions?.zodSchemaV4 || validationOptions?.zodSchemaV3;
|
|
4509
|
+
|
|
4510
|
+
if (!zodSchema) return;
|
|
4511
|
+
|
|
4512
|
+
// Get the full path including stateKey
|
|
4513
|
+
|
|
4514
|
+
// Update validation state to "validating"
|
|
4515
|
+
const currentMeta = getGlobalStore
|
|
4516
|
+
.getState()
|
|
4517
|
+
.getShadowMetadata(stateKey, path);
|
|
4518
|
+
getGlobalStore.getState().setShadowMetadata(stateKey, path, {
|
|
4519
|
+
...currentMeta,
|
|
4520
|
+
validation: {
|
|
4521
|
+
status: 'DIRTY',
|
|
4522
|
+
validatedValue: localValue,
|
|
4523
|
+
},
|
|
4524
|
+
});
|
|
4525
|
+
|
|
4526
|
+
// Validate full state
|
|
4527
|
+
const fullState = getGlobalStore.getState().getShadowValue(stateKey);
|
|
4528
|
+
const result = zodSchema.safeParse(fullState);
|
|
4529
|
+
console.log('result ', result);
|
|
4530
|
+
if (!result.success) {
|
|
4531
|
+
const errors =
|
|
4532
|
+
'issues' in result.error
|
|
4533
|
+
? result.error.issues
|
|
4534
|
+
: (result.error as any).errors;
|
|
4535
|
+
|
|
4536
|
+
console.log('All validation errors:', errors);
|
|
4537
|
+
console.log('Current blur path:', path);
|
|
4538
|
+
|
|
4539
|
+
// Find errors for this specific path
|
|
4540
|
+
const pathErrors = errors.filter((error: any) => {
|
|
4541
|
+
console.log('Processing error:', error);
|
|
4542
|
+
|
|
4543
|
+
// For array paths, we need to translate indices to ULIDs
|
|
4544
|
+
if (path.some((p) => p.startsWith('id:'))) {
|
|
4545
|
+
console.log('Detected array path with ULID');
|
|
4546
|
+
|
|
4547
|
+
// This is an array item path like ["id:xyz", "name"]
|
|
4548
|
+
const parentPath = path[0]!.startsWith('id:')
|
|
4549
|
+
? []
|
|
4550
|
+
: path.slice(0, -1);
|
|
4551
|
+
|
|
4552
|
+
console.log('Parent path:', parentPath);
|
|
4553
|
+
|
|
4554
|
+
const arrayMeta = getGlobalStore
|
|
4555
|
+
.getState()
|
|
4556
|
+
.getShadowMetadata(stateKey, parentPath);
|
|
4557
|
+
|
|
4558
|
+
console.log('Array metadata:', arrayMeta);
|
|
4559
|
+
|
|
4560
|
+
if (arrayMeta?.arrayKeys) {
|
|
4561
|
+
const itemKey = [stateKey, ...path.slice(0, -1)].join('.');
|
|
4562
|
+
const itemIndex = arrayMeta.arrayKeys.indexOf(itemKey);
|
|
4563
|
+
|
|
4564
|
+
console.log('Item key:', itemKey, 'Index:', itemIndex);
|
|
4565
|
+
|
|
4566
|
+
// Compare with Zod path
|
|
4567
|
+
const zodPath = [...parentPath, itemIndex, ...path.slice(-1)];
|
|
4568
|
+
const match =
|
|
4569
|
+
JSON.stringify(error.path) === JSON.stringify(zodPath);
|
|
4570
|
+
|
|
4571
|
+
console.log('Zod path comparison:', {
|
|
4572
|
+
zodPath,
|
|
4573
|
+
errorPath: error.path,
|
|
4574
|
+
match,
|
|
4575
|
+
});
|
|
4576
|
+
return match;
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
const directMatch = JSON.stringify(error.path) === JSON.stringify(path);
|
|
4581
|
+
console.log('Direct path comparison:', {
|
|
4582
|
+
errorPath: error.path,
|
|
4583
|
+
currentPath: path,
|
|
4584
|
+
match: directMatch,
|
|
4585
|
+
});
|
|
4586
|
+
return directMatch;
|
|
4587
|
+
});
|
|
4588
|
+
|
|
4589
|
+
console.log('Filtered path errors:', pathErrors);
|
|
4590
|
+
// Update shadow metadata with validation result
|
|
4591
|
+
getGlobalStore.getState().setShadowMetadata(stateKey, path, {
|
|
4592
|
+
...currentMeta,
|
|
4593
|
+
validation: {
|
|
4594
|
+
status: 'VALIDATION_FAILED',
|
|
4595
|
+
message: pathErrors[0]?.message,
|
|
4596
|
+
validatedValue: localValue,
|
|
4597
|
+
},
|
|
4598
|
+
});
|
|
4599
|
+
} else {
|
|
4600
|
+
// Validation passed
|
|
4601
|
+
getGlobalStore.getState().setShadowMetadata(stateKey, path, {
|
|
4602
|
+
...currentMeta,
|
|
4603
|
+
validation: {
|
|
4604
|
+
status: 'VALID_PENDING_SYNC',
|
|
4605
|
+
validatedValue: localValue,
|
|
4606
|
+
},
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4609
|
+
forceUpdate({});
|
|
4610
|
+
}, [stateKey, path, localValue, setState]);
|
|
4288
4611
|
|
|
4289
4612
|
const baseState = rebuildStateShape({
|
|
4290
4613
|
currentState: globalStateValue,
|
|
@@ -4300,7 +4623,8 @@ function FormElementWrapper({
|
|
|
4300
4623
|
onChange: (e: any) => {
|
|
4301
4624
|
debouncedUpdate(e.target.value);
|
|
4302
4625
|
},
|
|
4303
|
-
onBlur
|
|
4626
|
+
// 5. Wire the new onBlur handler to the input props.
|
|
4627
|
+
onBlur: handleBlur,
|
|
4304
4628
|
ref: formRefStore
|
|
4305
4629
|
.getState()
|
|
4306
4630
|
.getFormRef(stateKey + '.' + path.join('.')),
|
|
@@ -4311,9 +4635,12 @@ function FormElementWrapper({
|
|
|
4311
4635
|
},
|
|
4312
4636
|
});
|
|
4313
4637
|
|
|
4314
|
-
return
|
|
4638
|
+
return (
|
|
4639
|
+
<ValidationWrapper formOpts={formOpts} path={path} stateKey={stateKey}>
|
|
4640
|
+
{renderFn(stateWithInputProps)}
|
|
4641
|
+
</ValidationWrapper>
|
|
4642
|
+
);
|
|
4315
4643
|
}
|
|
4316
|
-
|
|
4317
4644
|
function useRegisterComponent(
|
|
4318
4645
|
stateKey: string,
|
|
4319
4646
|
componentId: string,
|
|
@@ -4322,25 +4649,19 @@ function useRegisterComponent(
|
|
|
4322
4649
|
const fullComponentId = `${stateKey}////${componentId}`;
|
|
4323
4650
|
|
|
4324
4651
|
useLayoutEffect(() => {
|
|
4325
|
-
const
|
|
4326
|
-
|
|
4652
|
+
const { registerComponent, unregisterComponent } =
|
|
4653
|
+
getGlobalStore.getState();
|
|
4327
4654
|
|
|
4328
|
-
|
|
4655
|
+
// Call the safe, centralized function to register
|
|
4656
|
+
registerComponent(stateKey, fullComponentId, {
|
|
4329
4657
|
forceUpdate: () => forceUpdate({}),
|
|
4330
4658
|
paths: new Set(),
|
|
4331
4659
|
reactiveType: ['component'],
|
|
4332
4660
|
});
|
|
4333
4661
|
|
|
4334
|
-
|
|
4335
|
-
...rootMeta,
|
|
4336
|
-
components,
|
|
4337
|
-
});
|
|
4338
|
-
|
|
4662
|
+
// The cleanup now calls the safe, centralized unregister function
|
|
4339
4663
|
return () => {
|
|
4340
|
-
|
|
4341
|
-
if (meta?.components) {
|
|
4342
|
-
meta.components.delete(fullComponentId);
|
|
4343
|
-
}
|
|
4664
|
+
unregisterComponent(stateKey, fullComponentId);
|
|
4344
4665
|
};
|
|
4345
|
-
}, [stateKey, fullComponentId]);
|
|
4666
|
+
}, [stateKey, fullComponentId]); // Dependencies are stable and correct
|
|
4346
4667
|
}
|