cogsbox-state 0.5.464 → 0.5.465
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 +10 -30
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1466 -1490
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Functions.d.ts.map +1 -1
- package/dist/Functions.jsx +23 -16
- package/dist/Functions.jsx.map +1 -1
- package/dist/index.js +26 -26
- package/dist/store.d.ts +39 -47
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +259 -295
- package/dist/store.js.map +1 -1
- package/dist/utility.d.ts +0 -1
- package/dist/utility.d.ts.map +1 -1
- package/dist/utility.js +121 -158
- package/dist/utility.js.map +1 -1
- package/package.json +5 -4
- package/src/CogsState.tsx +1047 -1392
- package/src/Functions.tsx +27 -7
- package/src/store.ts +489 -593
- package/src/utility.ts +0 -65
package/src/CogsState.tsx
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
} from 'react';
|
|
17
17
|
import { createRoot } from 'react-dom/client';
|
|
18
18
|
import {
|
|
19
|
-
debounce,
|
|
20
19
|
getDifferences,
|
|
21
20
|
isArray,
|
|
22
21
|
isFunction,
|
|
@@ -28,8 +27,11 @@ import superjson from 'superjson';
|
|
|
28
27
|
import { v4 as uuidv4 } from 'uuid';
|
|
29
28
|
|
|
30
29
|
import {
|
|
30
|
+
buildShadowNode,
|
|
31
31
|
formRefStore,
|
|
32
32
|
getGlobalStore,
|
|
33
|
+
METADATA_KEYS,
|
|
34
|
+
ValidationError,
|
|
33
35
|
ValidationStatus,
|
|
34
36
|
type ComponentsType,
|
|
35
37
|
} from './store.js';
|
|
@@ -38,7 +40,6 @@ import { Operation } from 'fast-json-patch';
|
|
|
38
40
|
import { useInView } from 'react-intersection-observer';
|
|
39
41
|
import * as z3 from 'zod/v3';
|
|
40
42
|
import * as z4 from 'zod/v4';
|
|
41
|
-
import z from 'zod';
|
|
42
43
|
|
|
43
44
|
type Prettify<T> = T extends any ? { [K in keyof T]: T[K] } : never;
|
|
44
45
|
|
|
@@ -245,10 +246,7 @@ export type UpdateType<T> = (payload: UpdateArg<T>) => { synced: () => void };
|
|
|
245
246
|
|
|
246
247
|
export type InsertType<T> = (payload: InsertParams<T>, index?: number) => void;
|
|
247
248
|
export type InsertTypeObj<T> = (payload: InsertParams<T>) => void;
|
|
248
|
-
|
|
249
|
-
path: (string | number)[];
|
|
250
|
-
message: string;
|
|
251
|
-
};
|
|
249
|
+
|
|
252
250
|
type EffectFunction<T, R> = (state: T, deps: any[]) => R;
|
|
253
251
|
export type EndType<T, IsArrayElement = false> = {
|
|
254
252
|
addZodValidation: (errors: ValidationError[]) => void;
|
|
@@ -259,7 +257,7 @@ export type EndType<T, IsArrayElement = false> = {
|
|
|
259
257
|
_stateKey: string;
|
|
260
258
|
formElement: (control: FormControl<T>, opts?: FormOptsType) => JSX.Element;
|
|
261
259
|
get: () => T;
|
|
262
|
-
|
|
260
|
+
|
|
263
261
|
$get: () => T;
|
|
264
262
|
$derive: <R>(fn: EffectFunction<T, R>) => R;
|
|
265
263
|
|
|
@@ -306,7 +304,7 @@ export type StateObject<T> = (T extends any[]
|
|
|
306
304
|
_isLoading: boolean;
|
|
307
305
|
_serverState: T;
|
|
308
306
|
revertToInitialState: (obj?: { validationKey?: string }) => T;
|
|
309
|
-
|
|
307
|
+
|
|
310
308
|
middleware: (
|
|
311
309
|
middles: ({
|
|
312
310
|
updateLog,
|
|
@@ -337,7 +335,8 @@ type UpdateOptions = {
|
|
|
337
335
|
type EffectiveSetState<TStateObject> = (
|
|
338
336
|
newStateOrFunction:
|
|
339
337
|
| EffectiveSetStateArg<TStateObject, 'update'>
|
|
340
|
-
| EffectiveSetStateArg<TStateObject, 'insert'
|
|
338
|
+
| EffectiveSetStateArg<TStateObject, 'insert'>
|
|
339
|
+
| null,
|
|
341
340
|
path: string[],
|
|
342
341
|
updateObj: UpdateOptions,
|
|
343
342
|
validationKey?: string
|
|
@@ -449,10 +448,13 @@ type FormsElementsType<T> = {
|
|
|
449
448
|
children: React.ReactNode;
|
|
450
449
|
status: ValidationStatus; // Instead of 'active' boolean
|
|
451
450
|
|
|
451
|
+
hasErrors: boolean;
|
|
452
|
+
hasWarnings: boolean;
|
|
453
|
+
allErrors: ValidationError[];
|
|
454
|
+
|
|
452
455
|
path: string[];
|
|
453
456
|
message?: string;
|
|
454
|
-
|
|
455
|
-
key?: string;
|
|
457
|
+
getData?: () => T;
|
|
456
458
|
}) => React.ReactNode;
|
|
457
459
|
syncRender?: (options: SyncRenderOptions<T>) => React.ReactNode;
|
|
458
460
|
};
|
|
@@ -476,11 +478,74 @@ export type TransformedStateType<T> = {
|
|
|
476
478
|
[P in keyof T]: T[P] extends CogsInitialState<infer U> ? U : T[P];
|
|
477
479
|
};
|
|
478
480
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
481
|
+
const {
|
|
482
|
+
getInitialOptions,
|
|
483
|
+
updateInitialStateGlobal,
|
|
484
|
+
// ALIAS THE NEW FUNCTIONS TO THE OLD NAMES
|
|
485
|
+
getShadowMetadata,
|
|
486
|
+
setShadowMetadata,
|
|
487
|
+
getShadowValue,
|
|
488
|
+
initializeShadowState,
|
|
489
|
+
updateShadowAtPath,
|
|
490
|
+
insertShadowArrayElement,
|
|
491
|
+
removeShadowArrayElement,
|
|
492
|
+
getSelectedIndex,
|
|
493
|
+
setInitialStateOptions,
|
|
494
|
+
setServerStateUpdate,
|
|
495
|
+
markAsDirty,
|
|
496
|
+
registerComponent,
|
|
497
|
+
unregisterComponent,
|
|
498
|
+
addPathComponent,
|
|
499
|
+
clearSelectedIndexesForState,
|
|
500
|
+
addStateLog,
|
|
501
|
+
setSyncInfo,
|
|
502
|
+
clearSelectedIndex,
|
|
503
|
+
getSyncInfo,
|
|
504
|
+
notifyPathSubscribers,
|
|
505
|
+
subscribeToPath,
|
|
506
|
+
// Note: The old functions are no longer imported under their original names
|
|
507
|
+
} = getGlobalStore.getState();
|
|
508
|
+
function getArrayData(stateKey: string, path: string[], meta?: MetaData) {
|
|
509
|
+
const shadowMeta = getShadowMetadata(stateKey, path);
|
|
510
|
+
const isArray = !!shadowMeta?.arrayKeys;
|
|
483
511
|
|
|
512
|
+
if (!isArray) {
|
|
513
|
+
const value = getGlobalStore.getState().getShadowValue(stateKey, path);
|
|
514
|
+
return { isArray: false, value, keys: [] };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const arrayPathKey = path.join('.');
|
|
518
|
+
const viewIds = meta?.arrayViews?.[arrayPathKey] ?? shadowMeta.arrayKeys;
|
|
519
|
+
|
|
520
|
+
// FIX: If the derived view is empty, return an empty array and keys.
|
|
521
|
+
if (Array.isArray(viewIds) && viewIds.length === 0) {
|
|
522
|
+
return { isArray: true, value: [], keys: [] };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const value = getGlobalStore
|
|
526
|
+
.getState()
|
|
527
|
+
.getShadowValue(stateKey, path, viewIds);
|
|
528
|
+
|
|
529
|
+
return { isArray: true, value, keys: viewIds ?? [] };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function findArrayItem(
|
|
533
|
+
array: any[],
|
|
534
|
+
keys: string[],
|
|
535
|
+
predicate: (item: any, index: number) => boolean
|
|
536
|
+
): { key: string; index: number; value: any } | null {
|
|
537
|
+
for (let i = 0; i < array.length; i++) {
|
|
538
|
+
if (predicate(array[i], i)) {
|
|
539
|
+
const key = keys[i];
|
|
540
|
+
if (key) {
|
|
541
|
+
return { key, index: i, value: array[i] };
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function setAndMergeOptions(stateKey: string, newOptions: OptionsType<any>) {
|
|
484
549
|
const initialOptions = getInitialOptions(stateKey as string) || {};
|
|
485
550
|
|
|
486
551
|
setInitialStateOptions(stateKey as string, {
|
|
@@ -499,8 +564,7 @@ function setOptions<StateKey, Opt>({
|
|
|
499
564
|
}) {
|
|
500
565
|
const initialOptions = getInitialOptions(stateKey as string) || {};
|
|
501
566
|
const initialOptionsPartState = initialOptionsPart[stateKey as string] || {};
|
|
502
|
-
|
|
503
|
-
getGlobalStore.getState().setInitialStateOptions;
|
|
567
|
+
|
|
504
568
|
const mergedOptions = { ...initialOptionsPartState, ...initialOptions };
|
|
505
569
|
|
|
506
570
|
let needToAdd = false;
|
|
@@ -621,10 +685,10 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
621
685
|
const existingGlobalOptions = getInitialOptions(key);
|
|
622
686
|
|
|
623
687
|
if (!existingGlobalOptions) {
|
|
624
|
-
|
|
688
|
+
setInitialStateOptions(key, mergedOptions);
|
|
625
689
|
} else {
|
|
626
690
|
// Merge with existing global options
|
|
627
|
-
|
|
691
|
+
setInitialStateOptions(key, {
|
|
628
692
|
...existingGlobalOptions,
|
|
629
693
|
...mergedOptions,
|
|
630
694
|
});
|
|
@@ -633,9 +697,9 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
633
697
|
});
|
|
634
698
|
|
|
635
699
|
Object.keys(statePart).forEach((key) => {
|
|
636
|
-
|
|
700
|
+
initializeShadowState(key, statePart[key]);
|
|
637
701
|
});
|
|
638
|
-
|
|
702
|
+
console.log('new stateObject ', getGlobalStore.getState().shadowStateStore);
|
|
639
703
|
type StateKeys = keyof typeof statePart;
|
|
640
704
|
|
|
641
705
|
const useCogsState = <StateKey extends StateKeys>(
|
|
@@ -650,8 +714,7 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
650
714
|
initialOptionsPart,
|
|
651
715
|
});
|
|
652
716
|
const thiState =
|
|
653
|
-
|
|
654
|
-
statePart[stateKey as string];
|
|
717
|
+
getShadowValue(stateKey as string, []) || statePart[stateKey as string];
|
|
655
718
|
const partialState = options?.modifyState
|
|
656
719
|
? options.modifyState(thiState)
|
|
657
720
|
: thiState;
|
|
@@ -774,12 +837,7 @@ export function createCogsStateFromSync<
|
|
|
774
837
|
__syncSchemas: schemas,
|
|
775
838
|
}) as any;
|
|
776
839
|
}
|
|
777
|
-
const {
|
|
778
|
-
getInitialOptions,
|
|
779
840
|
|
|
780
|
-
addStateLog,
|
|
781
|
-
updateInitialStateGlobal,
|
|
782
|
-
} = getGlobalStore.getState();
|
|
783
841
|
const saveToLocalStorage = <T,>(
|
|
784
842
|
state: T,
|
|
785
843
|
thisKey: string,
|
|
@@ -811,7 +869,7 @@ const saveToLocalStorage = <T,>(
|
|
|
811
869
|
} catch {
|
|
812
870
|
// Ignore errors, will use undefined
|
|
813
871
|
}
|
|
814
|
-
const shadowMeta =
|
|
872
|
+
const shadowMeta = getShadowMetadata(thisKey, []);
|
|
815
873
|
|
|
816
874
|
const data: LocalStorageData<T> = {
|
|
817
875
|
state,
|
|
@@ -847,7 +905,7 @@ const loadFromLocalStorage = (localStorageKey: string) => {
|
|
|
847
905
|
}
|
|
848
906
|
};
|
|
849
907
|
const loadAndApplyLocalStorage = (stateKey: string, options: any) => {
|
|
850
|
-
const currentState =
|
|
908
|
+
const currentState = getShadowValue(stateKey, []);
|
|
851
909
|
const { sessionId } = useCogsConfig();
|
|
852
910
|
const localkey = isFunction(options?.localStorage?.key)
|
|
853
911
|
? options.localStorage.key(currentState)
|
|
@@ -878,7 +936,7 @@ type LocalStorageData<T> = {
|
|
|
878
936
|
};
|
|
879
937
|
|
|
880
938
|
const notifyComponents = (thisKey: string) => {
|
|
881
|
-
const stateEntry =
|
|
939
|
+
const stateEntry = getShadowMetadata(thisKey, []);
|
|
882
940
|
if (!stateEntry) return;
|
|
883
941
|
|
|
884
942
|
// Batch component updates
|
|
@@ -900,40 +958,15 @@ const notifyComponents = (thisKey: string) => {
|
|
|
900
958
|
});
|
|
901
959
|
};
|
|
902
960
|
|
|
903
|
-
export const notifyComponent = (stateKey: string, componentId: string) => {
|
|
904
|
-
const stateEntry = getGlobalStore.getState().getShadowMetadata(stateKey, []);
|
|
905
|
-
if (stateEntry) {
|
|
906
|
-
const fullComponentId = `${stateKey}////${componentId}`;
|
|
907
|
-
const component = stateEntry?.components?.get(fullComponentId);
|
|
908
|
-
const reactiveTypes = component
|
|
909
|
-
? Array.isArray(component.reactiveType)
|
|
910
|
-
? component.reactiveType
|
|
911
|
-
: [component.reactiveType || 'component']
|
|
912
|
-
: null;
|
|
913
|
-
|
|
914
|
-
// Skip if reactivity is disabled
|
|
915
|
-
if (reactiveTypes?.includes('none')) {
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
if (component) {
|
|
920
|
-
// Force an update to ensure the current value is saved
|
|
921
|
-
|
|
922
|
-
component.forceUpdate();
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
};
|
|
926
961
|
function markEntireStateAsServerSynced(
|
|
927
962
|
stateKey: string,
|
|
928
963
|
path: string[],
|
|
929
964
|
data: any,
|
|
930
965
|
timestamp: number
|
|
931
966
|
) {
|
|
932
|
-
const store = getGlobalStore.getState();
|
|
933
|
-
|
|
934
967
|
// Mark current path as synced
|
|
935
|
-
const currentMeta =
|
|
936
|
-
|
|
968
|
+
const currentMeta = getShadowMetadata(stateKey, path);
|
|
969
|
+
setShadowMetadata(stateKey, path, {
|
|
937
970
|
...currentMeta,
|
|
938
971
|
isDirty: false,
|
|
939
972
|
stateSource: 'server',
|
|
@@ -942,10 +975,11 @@ function markEntireStateAsServerSynced(
|
|
|
942
975
|
|
|
943
976
|
// If it's an array, mark each item as synced
|
|
944
977
|
if (Array.isArray(data)) {
|
|
945
|
-
const arrayMeta =
|
|
978
|
+
const arrayMeta = getShadowMetadata(stateKey, path);
|
|
946
979
|
if (arrayMeta?.arrayKeys) {
|
|
947
980
|
arrayMeta.arrayKeys.forEach((itemKey, index) => {
|
|
948
|
-
|
|
981
|
+
// Fix: Don't split the itemKey, just use it directly
|
|
982
|
+
const itemPath = [...path, itemKey];
|
|
949
983
|
const itemData = data[index];
|
|
950
984
|
if (itemData !== undefined) {
|
|
951
985
|
markEntireStateAsServerSynced(
|
|
@@ -967,8 +1001,346 @@ function markEntireStateAsServerSynced(
|
|
|
967
1001
|
});
|
|
968
1002
|
}
|
|
969
1003
|
}
|
|
970
|
-
|
|
971
|
-
let
|
|
1004
|
+
// 5. Batch queue
|
|
1005
|
+
let updateBatchQueue: any[] = [];
|
|
1006
|
+
let isFlushScheduled = false;
|
|
1007
|
+
|
|
1008
|
+
function scheduleFlush() {
|
|
1009
|
+
if (!isFlushScheduled) {
|
|
1010
|
+
isFlushScheduled = true;
|
|
1011
|
+
queueMicrotask(flushQueue);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
function handleUpdate(
|
|
1015
|
+
stateKey: string,
|
|
1016
|
+
path: string[],
|
|
1017
|
+
payload: any
|
|
1018
|
+
): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
|
|
1019
|
+
// ✅ FIX: Get the old value before the update.
|
|
1020
|
+
const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
|
|
1021
|
+
|
|
1022
|
+
const newValue = isFunction(payload) ? payload(oldValue) : payload;
|
|
1023
|
+
|
|
1024
|
+
// ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
|
|
1025
|
+
// The manual loop has been removed.
|
|
1026
|
+
updateShadowAtPath(stateKey, path, newValue);
|
|
1027
|
+
|
|
1028
|
+
markAsDirty(stateKey, path, { bubble: true });
|
|
1029
|
+
|
|
1030
|
+
// Return the metadata of the node *after* the update.
|
|
1031
|
+
const newShadowMeta = getShadowMetadata(stateKey, path);
|
|
1032
|
+
|
|
1033
|
+
return {
|
|
1034
|
+
type: 'update',
|
|
1035
|
+
oldValue: oldValue,
|
|
1036
|
+
newValue,
|
|
1037
|
+
shadowMeta: newShadowMeta,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
// 2. Update signals
|
|
1041
|
+
function updateSignals(shadowMeta: any, displayValue: any) {
|
|
1042
|
+
if (!shadowMeta?.signals?.length) return;
|
|
1043
|
+
|
|
1044
|
+
shadowMeta.signals.forEach(({ parentId, position, effect }: any) => {
|
|
1045
|
+
const parent = document.querySelector(`[data-parent-id="${parentId}"]`);
|
|
1046
|
+
if (!parent) return;
|
|
1047
|
+
|
|
1048
|
+
const childNodes = Array.from(parent.childNodes);
|
|
1049
|
+
if (!childNodes[position]) return;
|
|
1050
|
+
|
|
1051
|
+
let finalDisplayValue = displayValue;
|
|
1052
|
+
if (effect && displayValue !== null) {
|
|
1053
|
+
try {
|
|
1054
|
+
finalDisplayValue = new Function('state', `return (${effect})(state)`)(
|
|
1055
|
+
displayValue
|
|
1056
|
+
);
|
|
1057
|
+
} catch (err) {
|
|
1058
|
+
console.error('Error evaluating effect function:', err);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (finalDisplayValue !== null && typeof finalDisplayValue === 'object') {
|
|
1063
|
+
finalDisplayValue = JSON.stringify(finalDisplayValue);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
childNodes[position].textContent = String(finalDisplayValue ?? '');
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function getComponentNotifications(
|
|
1071
|
+
stateKey: string,
|
|
1072
|
+
path: string[],
|
|
1073
|
+
result: any
|
|
1074
|
+
): Set<any> {
|
|
1075
|
+
const rootMeta = getShadowMetadata(stateKey, []);
|
|
1076
|
+
|
|
1077
|
+
if (!rootMeta?.components) {
|
|
1078
|
+
return new Set();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const componentsToNotify = new Set<any>();
|
|
1082
|
+
|
|
1083
|
+
// --- PASS 1: Notify specific subscribers based on update type ---
|
|
1084
|
+
|
|
1085
|
+
if (result.type === 'update') {
|
|
1086
|
+
// --- Bubble-up Notification ---
|
|
1087
|
+
// An update to `user.address.street` notifies listeners of `street`, `address`, and `user`.
|
|
1088
|
+
let currentPath = [...path];
|
|
1089
|
+
while (true) {
|
|
1090
|
+
const pathMeta = getShadowMetadata(stateKey, currentPath);
|
|
1091
|
+
|
|
1092
|
+
if (pathMeta?.pathComponents) {
|
|
1093
|
+
pathMeta.pathComponents.forEach((componentId: string) => {
|
|
1094
|
+
const component = rootMeta.components?.get(componentId);
|
|
1095
|
+
// NEW: Add component to the set instead of calling forceUpdate()
|
|
1096
|
+
if (component) {
|
|
1097
|
+
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1098
|
+
? component.reactiveType
|
|
1099
|
+
: [component.reactiveType || 'component'];
|
|
1100
|
+
if (!reactiveTypes.includes('none')) {
|
|
1101
|
+
componentsToNotify.add(component);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (currentPath.length === 0) break;
|
|
1108
|
+
currentPath.pop(); // Go up one level
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// --- Deep Object Change Notification ---
|
|
1112
|
+
// If the new value is an object, notify components subscribed to sub-paths that changed.
|
|
1113
|
+
if (
|
|
1114
|
+
result.newValue &&
|
|
1115
|
+
typeof result.newValue === 'object' &&
|
|
1116
|
+
!isArray(result.newValue)
|
|
1117
|
+
) {
|
|
1118
|
+
const changedSubPaths = getDifferences(result.newValue, result.oldValue);
|
|
1119
|
+
|
|
1120
|
+
changedSubPaths.forEach((subPathString: string) => {
|
|
1121
|
+
const subPath = subPathString.split('.');
|
|
1122
|
+
const fullSubPath = [...path, ...subPath];
|
|
1123
|
+
const subPathMeta = getShadowMetadata(stateKey, fullSubPath);
|
|
1124
|
+
|
|
1125
|
+
if (subPathMeta?.pathComponents) {
|
|
1126
|
+
subPathMeta.pathComponents.forEach((componentId: string) => {
|
|
1127
|
+
const component = rootMeta.components?.get(componentId);
|
|
1128
|
+
// NEW: Add component to the set
|
|
1129
|
+
if (component) {
|
|
1130
|
+
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1131
|
+
? component.reactiveType
|
|
1132
|
+
: [component.reactiveType || 'component'];
|
|
1133
|
+
if (!reactiveTypes.includes('none')) {
|
|
1134
|
+
componentsToNotify.add(component);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
} else if (result.type === 'insert' || result.type === 'cut') {
|
|
1142
|
+
// For array structural changes (add/remove), notify components listening to the parent array.
|
|
1143
|
+
const parentArrayPath = result.type === 'insert' ? path : path.slice(0, -1);
|
|
1144
|
+
const parentMeta = getShadowMetadata(stateKey, parentArrayPath);
|
|
1145
|
+
|
|
1146
|
+
if (parentMeta?.pathComponents) {
|
|
1147
|
+
parentMeta.pathComponents.forEach((componentId: string) => {
|
|
1148
|
+
const component = rootMeta.components?.get(componentId);
|
|
1149
|
+
// NEW: Add component to the set
|
|
1150
|
+
if (component) {
|
|
1151
|
+
componentsToNotify.add(component);
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// --- PASS 2: Handle 'all' and 'deps' reactivity types ---
|
|
1158
|
+
// Iterate over all components for this stateKey that haven't been notified yet.
|
|
1159
|
+
rootMeta.components.forEach((component, componentId) => {
|
|
1160
|
+
// If we've already added this component, skip it.
|
|
1161
|
+
if (componentsToNotify.has(component)) {
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1166
|
+
? component.reactiveType
|
|
1167
|
+
: [component.reactiveType || 'component'];
|
|
1168
|
+
|
|
1169
|
+
if (reactiveTypes.includes('all')) {
|
|
1170
|
+
componentsToNotify.add(component);
|
|
1171
|
+
} else if (reactiveTypes.includes('deps') && component.depsFunction) {
|
|
1172
|
+
const currentState = getShadowValue(stateKey, []);
|
|
1173
|
+
const newDeps = component.depsFunction(currentState);
|
|
1174
|
+
|
|
1175
|
+
if (
|
|
1176
|
+
newDeps === true ||
|
|
1177
|
+
(Array.isArray(newDeps) && !isDeepEqual(component.prevDeps, newDeps))
|
|
1178
|
+
) {
|
|
1179
|
+
component.prevDeps = newDeps as any; // Update the dependencies for the next check
|
|
1180
|
+
componentsToNotify.add(component);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
return componentsToNotify;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function handleInsert(
|
|
1189
|
+
stateKey: string,
|
|
1190
|
+
path: string[],
|
|
1191
|
+
payload: any
|
|
1192
|
+
): { type: 'insert'; newValue: any; shadowMeta: any } {
|
|
1193
|
+
let newValue;
|
|
1194
|
+
if (isFunction(payload)) {
|
|
1195
|
+
const { value: currentValue } = getScopedData(stateKey, path);
|
|
1196
|
+
newValue = payload({ state: currentValue, uuid: uuidv4() });
|
|
1197
|
+
} else {
|
|
1198
|
+
newValue = payload;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
insertShadowArrayElement(stateKey, path, newValue);
|
|
1202
|
+
markAsDirty(stateKey, path, { bubble: true });
|
|
1203
|
+
|
|
1204
|
+
const updatedMeta = getShadowMetadata(stateKey, path);
|
|
1205
|
+
if (updatedMeta?.arrayKeys) {
|
|
1206
|
+
const newItemKey = updatedMeta.arrayKeys[updatedMeta.arrayKeys.length - 1];
|
|
1207
|
+
if (newItemKey) {
|
|
1208
|
+
const newItemPath = newItemKey.split('.').slice(1);
|
|
1209
|
+
markAsDirty(stateKey, newItemPath, { bubble: false });
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
return { type: 'insert', newValue, shadowMeta: updatedMeta };
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
function handleCut(
|
|
1217
|
+
stateKey: string,
|
|
1218
|
+
path: string[]
|
|
1219
|
+
): { type: 'cut'; oldValue: any; parentPath: string[] } {
|
|
1220
|
+
const parentArrayPath = path.slice(0, -1);
|
|
1221
|
+
const oldValue = getShadowValue(stateKey, path);
|
|
1222
|
+
removeShadowArrayElement(stateKey, path);
|
|
1223
|
+
markAsDirty(stateKey, parentArrayPath, { bubble: true });
|
|
1224
|
+
return { type: 'cut', oldValue: oldValue, parentPath: parentArrayPath };
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function flushQueue() {
|
|
1228
|
+
const allComponentsToNotify = new Set<any>();
|
|
1229
|
+
const signalUpdates: { shadowMeta: any; displayValue: any }[] = [];
|
|
1230
|
+
|
|
1231
|
+
const logsToAdd: UpdateTypeDetail[] = [];
|
|
1232
|
+
|
|
1233
|
+
for (const item of updateBatchQueue) {
|
|
1234
|
+
if (item.status && item.updateType) {
|
|
1235
|
+
logsToAdd.push(item as UpdateTypeDetail);
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const result = item;
|
|
1240
|
+
|
|
1241
|
+
const displayValue = result.type === 'cut' ? null : result.newValue;
|
|
1242
|
+
if (result.shadowMeta?.signals?.length > 0) {
|
|
1243
|
+
signalUpdates.push({ shadowMeta: result.shadowMeta, displayValue });
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const componentNotifications = getComponentNotifications(
|
|
1247
|
+
result.stateKey,
|
|
1248
|
+
result.path,
|
|
1249
|
+
result
|
|
1250
|
+
);
|
|
1251
|
+
|
|
1252
|
+
componentNotifications.forEach((component) => {
|
|
1253
|
+
allComponentsToNotify.add(component);
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
if (logsToAdd.length > 0) {
|
|
1258
|
+
addStateLog(logsToAdd);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
signalUpdates.forEach(({ shadowMeta, displayValue }) => {
|
|
1262
|
+
updateSignals(shadowMeta, displayValue);
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
allComponentsToNotify.forEach((component) => {
|
|
1266
|
+
component.forceUpdate();
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
// --- Step 3: CLEANUP ---
|
|
1270
|
+
// Clear the queue for the next batch of updates.
|
|
1271
|
+
updateBatchQueue = [];
|
|
1272
|
+
isFlushScheduled = false;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function createEffectiveSetState<T>(
|
|
1276
|
+
thisKey: string,
|
|
1277
|
+
syncApiRef: React.MutableRefObject<any>,
|
|
1278
|
+
sessionId: string | undefined,
|
|
1279
|
+
latestInitialOptionsRef: React.MutableRefObject<OptionsType<T> | null>
|
|
1280
|
+
): EffectiveSetState<T> {
|
|
1281
|
+
// The returned function is the core setter that gets called by all state operations.
|
|
1282
|
+
// It is now much simpler, delegating all work to the executeUpdate function.
|
|
1283
|
+
return (newStateOrFunction, path, updateObj, validationKey?) => {
|
|
1284
|
+
executeUpdate(thisKey, path, newStateOrFunction, updateObj);
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
// This inner function handles the logic for a single state update.
|
|
1288
|
+
function executeUpdate(
|
|
1289
|
+
stateKey: string,
|
|
1290
|
+
path: string[],
|
|
1291
|
+
payload: any,
|
|
1292
|
+
options: UpdateOptions
|
|
1293
|
+
) {
|
|
1294
|
+
// --- Step 1: Execute the core state change (Synchronous & Fast) ---
|
|
1295
|
+
// This part modifies the in-memory state representation immediately.
|
|
1296
|
+
let result: any;
|
|
1297
|
+
switch (options.updateType) {
|
|
1298
|
+
case 'update':
|
|
1299
|
+
result = handleUpdate(stateKey, path, payload);
|
|
1300
|
+
break;
|
|
1301
|
+
case 'insert':
|
|
1302
|
+
result = handleInsert(stateKey, path, payload);
|
|
1303
|
+
break;
|
|
1304
|
+
case 'cut':
|
|
1305
|
+
result = handleCut(stateKey, path);
|
|
1306
|
+
break;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
result.stateKey = stateKey;
|
|
1310
|
+
result.path = path;
|
|
1311
|
+
updateBatchQueue.push(result);
|
|
1312
|
+
scheduleFlush();
|
|
1313
|
+
|
|
1314
|
+
const newUpdate: UpdateTypeDetail = {
|
|
1315
|
+
timeStamp: Date.now(),
|
|
1316
|
+
stateKey,
|
|
1317
|
+
path,
|
|
1318
|
+
updateType: options.updateType,
|
|
1319
|
+
status: 'new',
|
|
1320
|
+
oldValue: result.oldValue,
|
|
1321
|
+
newValue: result.newValue ?? null,
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
updateBatchQueue.push(newUpdate);
|
|
1325
|
+
|
|
1326
|
+
if (result.newValue !== undefined) {
|
|
1327
|
+
saveToLocalStorage(
|
|
1328
|
+
result.newValue,
|
|
1329
|
+
stateKey,
|
|
1330
|
+
latestInitialOptionsRef.current,
|
|
1331
|
+
sessionId
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
if (latestInitialOptionsRef.current?.middleware) {
|
|
1336
|
+
latestInitialOptionsRef.current.middleware({ update: newUpdate });
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
if (options.sync !== false && syncApiRef.current?.connected) {
|
|
1340
|
+
syncApiRef.current.updateState({ operation: newUpdate });
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
972
1344
|
|
|
973
1345
|
export function useCogsStateFn<TStateObject extends unknown>(
|
|
974
1346
|
stateObject: TStateObject,
|
|
@@ -1007,7 +1379,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1007
1379
|
useEffect(() => {
|
|
1008
1380
|
if (syncUpdate && syncUpdate.stateKey === thisKey && syncUpdate.path?.[0]) {
|
|
1009
1381
|
const syncKey = `${syncUpdate.stateKey}:${syncUpdate.path.join('.')}`;
|
|
1010
|
-
|
|
1382
|
+
setSyncInfo(syncKey, {
|
|
1011
1383
|
timeStamp: syncUpdate.timeStamp!,
|
|
1012
1384
|
userId: syncUpdate.userId!,
|
|
1013
1385
|
});
|
|
@@ -1077,7 +1449,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1077
1449
|
|
|
1078
1450
|
// Effect 1: When this component's serverState prop changes, broadcast it
|
|
1079
1451
|
useEffect(() => {
|
|
1080
|
-
|
|
1452
|
+
setServerStateUpdate(thisKey, serverState);
|
|
1081
1453
|
}, [serverState, thisKey]);
|
|
1082
1454
|
|
|
1083
1455
|
// Effect 2: Listen for server state updates from ANY component
|
|
@@ -1087,115 +1459,79 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1087
1459
|
.subscribeToPath(thisKey, (event) => {
|
|
1088
1460
|
if (event?.type === 'SERVER_STATE_UPDATE') {
|
|
1089
1461
|
const serverStateData = event.serverState;
|
|
1090
|
-
|
|
1462
|
+
|
|
1091
1463
|
if (
|
|
1092
|
-
serverStateData?.status
|
|
1093
|
-
serverStateData.data
|
|
1464
|
+
serverStateData?.status !== 'success' ||
|
|
1465
|
+
serverStateData.data === undefined
|
|
1094
1466
|
) {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
const mergeConfig =
|
|
1099
|
-
typeof serverStateData.merge === 'object'
|
|
1100
|
-
? serverStateData.merge
|
|
1101
|
-
: serverStateData.merge === true
|
|
1102
|
-
? { strategy: 'append' }
|
|
1103
|
-
: null;
|
|
1467
|
+
return; // Ignore if no valid data
|
|
1468
|
+
}
|
|
1104
1469
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
if (
|
|
1110
|
-
mergeConfig &&
|
|
1111
|
-
Array.isArray(currentState) &&
|
|
1112
|
-
Array.isArray(incomingData)
|
|
1113
|
-
) {
|
|
1114
|
-
const keyField = mergeConfig.key;
|
|
1115
|
-
const existingIds = new Set(
|
|
1116
|
-
currentState.map((item: any) => item[keyField])
|
|
1117
|
-
);
|
|
1470
|
+
console.log(
|
|
1471
|
+
'✅ SERVER_STATE_UPDATE received with data:',
|
|
1472
|
+
serverStateData
|
|
1473
|
+
);
|
|
1118
1474
|
|
|
1119
|
-
|
|
1120
|
-
return !existingIds.has(item[keyField]);
|
|
1121
|
-
});
|
|
1475
|
+
setAndMergeOptions(thisKey, { serverState: serverStateData });
|
|
1122
1476
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1477
|
+
const mergeConfig =
|
|
1478
|
+
typeof serverStateData.merge === 'object'
|
|
1479
|
+
? serverStateData.merge
|
|
1480
|
+
: serverStateData.merge === true
|
|
1481
|
+
? { strategy: 'append' }
|
|
1482
|
+
: null;
|
|
1128
1483
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
.getShadowMetadata(thisKey, []);
|
|
1133
|
-
|
|
1134
|
-
if (arrayMeta?.arrayKeys) {
|
|
1135
|
-
const newItemKey =
|
|
1136
|
-
arrayMeta.arrayKeys[arrayMeta.arrayKeys.length - 1];
|
|
1137
|
-
if (newItemKey) {
|
|
1138
|
-
const newItemPath = newItemKey.split('.').slice(1);
|
|
1139
|
-
|
|
1140
|
-
// Mark the new server item as synced, not dirty
|
|
1141
|
-
getGlobalStore
|
|
1142
|
-
.getState()
|
|
1143
|
-
.setShadowMetadata(thisKey, newItemPath, {
|
|
1144
|
-
isDirty: false,
|
|
1145
|
-
stateSource: 'server',
|
|
1146
|
-
lastServerSync:
|
|
1147
|
-
serverStateData.timestamp || Date.now(),
|
|
1148
|
-
});
|
|
1484
|
+
// ✅ FIX 1: The path for the root value is now `[]`.
|
|
1485
|
+
const currentState = getShadowValue(thisKey, []);
|
|
1486
|
+
const incomingData = serverStateData.data;
|
|
1149
1487
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
getGlobalStore
|
|
1162
|
-
.getState()
|
|
1163
|
-
.setShadowMetadata(thisKey, fieldPath, {
|
|
1164
|
-
isDirty: false,
|
|
1165
|
-
stateSource: 'server',
|
|
1166
|
-
lastServerSync:
|
|
1167
|
-
serverStateData.timestamp || Date.now(),
|
|
1168
|
-
});
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
});
|
|
1174
|
-
}
|
|
1175
|
-
} else {
|
|
1176
|
-
// For replace strategy or initial load
|
|
1177
|
-
getGlobalStore
|
|
1178
|
-
.getState()
|
|
1179
|
-
.initializeShadowState(thisKey, incomingData);
|
|
1180
|
-
|
|
1181
|
-
// Mark the entire state tree as synced from server
|
|
1182
|
-
markEntireStateAsServerSynced(
|
|
1183
|
-
thisKey,
|
|
1184
|
-
[],
|
|
1185
|
-
incomingData,
|
|
1186
|
-
serverStateData.timestamp
|
|
1488
|
+
if (
|
|
1489
|
+
mergeConfig &&
|
|
1490
|
+
mergeConfig.strategy === 'append' &&
|
|
1491
|
+
'key' in mergeConfig && // Type guard for key
|
|
1492
|
+
Array.isArray(currentState) &&
|
|
1493
|
+
Array.isArray(incomingData)
|
|
1494
|
+
) {
|
|
1495
|
+
const keyField = mergeConfig.key;
|
|
1496
|
+
if (!keyField) {
|
|
1497
|
+
console.error(
|
|
1498
|
+
"CogsState: Merge strategy 'append' requires a 'key' field."
|
|
1187
1499
|
);
|
|
1500
|
+
return;
|
|
1188
1501
|
}
|
|
1502
|
+
console.log('SERVER_STATE_UPDATE 2');
|
|
1503
|
+
const existingIds = new Set(
|
|
1504
|
+
currentState.map((item: any) => item[keyField])
|
|
1505
|
+
);
|
|
1189
1506
|
|
|
1190
|
-
const
|
|
1191
|
-
.
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
}
|
|
1507
|
+
const newUniqueItems = incomingData.filter(
|
|
1508
|
+
(item: any) => !existingIds.has(item[keyField])
|
|
1509
|
+
);
|
|
1510
|
+
|
|
1511
|
+
if (newUniqueItems.length > 0) {
|
|
1512
|
+
newUniqueItems.forEach((item) => {
|
|
1513
|
+
insertShadowArrayElement(thisKey, [], item);
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
// Mark the entire final state as synced
|
|
1518
|
+
const finalState = getShadowValue(thisKey, []);
|
|
1519
|
+
markEntireStateAsServerSynced(
|
|
1520
|
+
thisKey,
|
|
1521
|
+
[],
|
|
1522
|
+
finalState,
|
|
1523
|
+
serverStateData.timestamp
|
|
1524
|
+
);
|
|
1525
|
+
} else {
|
|
1526
|
+
// This handles the "replace" strategy (initial load)
|
|
1527
|
+
initializeShadowState(thisKey, incomingData);
|
|
1528
|
+
|
|
1529
|
+
markEntireStateAsServerSynced(
|
|
1530
|
+
thisKey,
|
|
1531
|
+
[],
|
|
1532
|
+
incomingData,
|
|
1533
|
+
serverStateData.timestamp
|
|
1534
|
+
);
|
|
1199
1535
|
}
|
|
1200
1536
|
}
|
|
1201
1537
|
});
|
|
@@ -1213,6 +1549,17 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1213
1549
|
|
|
1214
1550
|
const options = getInitialOptions(thisKey as string);
|
|
1215
1551
|
|
|
1552
|
+
const features = {
|
|
1553
|
+
syncEnabled: !!cogsSyncFn && !!syncOpt,
|
|
1554
|
+
validationEnabled: !!(
|
|
1555
|
+
options?.validation?.zodSchemaV4 || options?.validation?.zodSchemaV3
|
|
1556
|
+
),
|
|
1557
|
+
localStorageEnabled: !!options?.localStorage?.key,
|
|
1558
|
+
};
|
|
1559
|
+
setShadowMetadata(thisKey, [], {
|
|
1560
|
+
...existingMeta,
|
|
1561
|
+
features,
|
|
1562
|
+
});
|
|
1216
1563
|
if (options?.defaultState !== undefined || defaultState !== undefined) {
|
|
1217
1564
|
const finalDefaultState = options?.defaultState || defaultState;
|
|
1218
1565
|
|
|
@@ -1225,10 +1572,10 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1225
1572
|
|
|
1226
1573
|
const { value: resolvedState, source, timestamp } = resolveInitialState();
|
|
1227
1574
|
|
|
1228
|
-
|
|
1575
|
+
initializeShadowState(thisKey, resolvedState);
|
|
1229
1576
|
|
|
1230
1577
|
// Set shadow metadata with the correct source info
|
|
1231
|
-
|
|
1578
|
+
setShadowMetadata(thisKey, [], {
|
|
1232
1579
|
stateSource: source,
|
|
1233
1580
|
lastServerSync: source === 'server' ? timestamp : undefined,
|
|
1234
1581
|
isDirty: false,
|
|
@@ -1252,29 +1599,27 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1252
1599
|
const componentKey = `${thisKey}////${componentIdRef.current}`;
|
|
1253
1600
|
|
|
1254
1601
|
// Register component in shadow metadata at root level
|
|
1255
|
-
const rootMeta =
|
|
1602
|
+
const rootMeta = getShadowMetadata(thisKey, []);
|
|
1256
1603
|
const components = rootMeta?.components || new Map();
|
|
1257
1604
|
|
|
1258
1605
|
components.set(componentKey, {
|
|
1259
1606
|
forceUpdate: () => forceUpdate({}),
|
|
1260
|
-
reactiveType: reactiveType ?? ['component'
|
|
1607
|
+
reactiveType: reactiveType ?? ['component'],
|
|
1261
1608
|
paths: new Set(),
|
|
1262
1609
|
depsFunction: reactiveDeps || undefined,
|
|
1263
|
-
deps: reactiveDeps
|
|
1264
|
-
? reactiveDeps(getGlobalStore.getState().getShadowValue(thisKey))
|
|
1265
|
-
: [],
|
|
1610
|
+
deps: reactiveDeps ? reactiveDeps(getShadowValue(thisKey, [])) : [],
|
|
1266
1611
|
prevDeps: reactiveDeps // Initialize prevDeps with the same initial value
|
|
1267
|
-
? reactiveDeps(
|
|
1612
|
+
? reactiveDeps(getShadowValue(thisKey, []))
|
|
1268
1613
|
: [],
|
|
1269
1614
|
});
|
|
1270
1615
|
|
|
1271
|
-
|
|
1616
|
+
setShadowMetadata(thisKey, [], {
|
|
1272
1617
|
...rootMeta,
|
|
1273
1618
|
components,
|
|
1274
1619
|
});
|
|
1275
1620
|
forceUpdate({});
|
|
1276
1621
|
return () => {
|
|
1277
|
-
const meta =
|
|
1622
|
+
const meta = getShadowMetadata(thisKey, []);
|
|
1278
1623
|
const component = meta?.components?.get(componentKey);
|
|
1279
1624
|
|
|
1280
1625
|
// Remove from each path we registered to
|
|
@@ -1302,448 +1647,18 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1302
1647
|
|
|
1303
1648
|
// Remove from root components
|
|
1304
1649
|
if (meta?.components) {
|
|
1305
|
-
|
|
1650
|
+
setShadowMetadata(thisKey, [], meta);
|
|
1306
1651
|
}
|
|
1307
1652
|
};
|
|
1308
1653
|
}, []);
|
|
1309
1654
|
|
|
1310
1655
|
const syncApiRef = useRef<SyncApi | null>(null);
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
)
|
|
1317
|
-
const fullPath = [thisKey, ...path].join('.');
|
|
1318
|
-
const store = getGlobalStore.getState();
|
|
1319
|
-
|
|
1320
|
-
const shadowMeta = store.getShadowMetadata(thisKey, path);
|
|
1321
|
-
const nestedShadowValue = store.getShadowValue(fullPath) as TStateObject;
|
|
1322
|
-
|
|
1323
|
-
const payload = (
|
|
1324
|
-
updateObj.updateType === 'insert' && isFunction(newStateOrFunction)
|
|
1325
|
-
? newStateOrFunction({ state: nestedShadowValue, uuid: uuidv4() })
|
|
1326
|
-
: isFunction(newStateOrFunction)
|
|
1327
|
-
? newStateOrFunction(nestedShadowValue)
|
|
1328
|
-
: newStateOrFunction
|
|
1329
|
-
) as TStateObject;
|
|
1330
|
-
|
|
1331
|
-
const timeStamp = Date.now();
|
|
1332
|
-
|
|
1333
|
-
const newUpdate = {
|
|
1334
|
-
timeStamp,
|
|
1335
|
-
stateKey: thisKey,
|
|
1336
|
-
path,
|
|
1337
|
-
updateType: updateObj.updateType,
|
|
1338
|
-
status: 'new' as const,
|
|
1339
|
-
oldValue: nestedShadowValue,
|
|
1340
|
-
newValue: payload,
|
|
1341
|
-
} satisfies UpdateTypeDetail;
|
|
1342
|
-
|
|
1343
|
-
// Perform the update
|
|
1344
|
-
switch (updateObj.updateType) {
|
|
1345
|
-
case 'insert': {
|
|
1346
|
-
store.insertShadowArrayElement(thisKey, path, newUpdate.newValue);
|
|
1347
|
-
store.markAsDirty(thisKey, path, { bubble: true });
|
|
1348
|
-
const arrayMeta = shadowMeta;
|
|
1349
|
-
if (arrayMeta?.arrayKeys) {
|
|
1350
|
-
const newItemKey =
|
|
1351
|
-
arrayMeta.arrayKeys[arrayMeta.arrayKeys.length - 1];
|
|
1352
|
-
if (newItemKey) {
|
|
1353
|
-
const newItemPath = newItemKey.split('.').slice(1); // Remove stateKey
|
|
1354
|
-
store.markAsDirty(thisKey, newItemPath, { bubble: false });
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
break;
|
|
1358
|
-
}
|
|
1359
|
-
case 'cut': {
|
|
1360
|
-
const parentArrayPath = path.slice(0, -1);
|
|
1361
|
-
|
|
1362
|
-
store.removeShadowArrayElement(thisKey, path);
|
|
1363
|
-
store.markAsDirty(thisKey, parentArrayPath, { bubble: true });
|
|
1364
|
-
break;
|
|
1365
|
-
}
|
|
1366
|
-
case 'update': {
|
|
1367
|
-
store.updateShadowAtPath(thisKey, path, newUpdate.newValue);
|
|
1368
|
-
store.markAsDirty(thisKey, path, { bubble: true });
|
|
1369
|
-
break;
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
const shouldSync = updateObj.sync !== false;
|
|
1374
|
-
|
|
1375
|
-
if (shouldSync && syncApiRef.current && syncApiRef.current.connected) {
|
|
1376
|
-
syncApiRef.current.updateState({ operation: newUpdate });
|
|
1377
|
-
}
|
|
1378
|
-
// Handle signals - reuse shadowMeta from the beginning
|
|
1379
|
-
if (shadowMeta?.signals && shadowMeta.signals.length > 0) {
|
|
1380
|
-
// Use updatedShadowValue if we need the new value, otherwise use payload
|
|
1381
|
-
const displayValue = updateObj.updateType === 'cut' ? null : payload;
|
|
1382
|
-
|
|
1383
|
-
shadowMeta.signals.forEach(({ parentId, position, effect }) => {
|
|
1384
|
-
const parent = document.querySelector(`[data-parent-id="${parentId}"]`);
|
|
1385
|
-
if (parent) {
|
|
1386
|
-
const childNodes = Array.from(parent.childNodes);
|
|
1387
|
-
if (childNodes[position]) {
|
|
1388
|
-
let finalDisplayValue = displayValue;
|
|
1389
|
-
if (effect && displayValue !== null) {
|
|
1390
|
-
try {
|
|
1391
|
-
finalDisplayValue = new Function(
|
|
1392
|
-
'state',
|
|
1393
|
-
`return (${effect})(state)`
|
|
1394
|
-
)(displayValue);
|
|
1395
|
-
} catch (err) {
|
|
1396
|
-
console.error('Error evaluating effect function:', err);
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
if (
|
|
1401
|
-
finalDisplayValue !== null &&
|
|
1402
|
-
finalDisplayValue !== undefined &&
|
|
1403
|
-
typeof finalDisplayValue === 'object'
|
|
1404
|
-
) {
|
|
1405
|
-
finalDisplayValue = JSON.stringify(finalDisplayValue) as any;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
childNodes[position].textContent = String(finalDisplayValue ?? '');
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
});
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
// Update in effectiveSetState for insert handling:
|
|
1415
|
-
if (updateObj.updateType === 'insert') {
|
|
1416
|
-
// Use shadowMeta from beginning if it's an array
|
|
1417
|
-
if (shadowMeta?.mapWrappers && shadowMeta.mapWrappers.length > 0) {
|
|
1418
|
-
// Get fresh array keys after insert
|
|
1419
|
-
const sourceArrayKeys =
|
|
1420
|
-
store.getShadowMetadata(thisKey, path)?.arrayKeys || [];
|
|
1421
|
-
const newItemKey = sourceArrayKeys[sourceArrayKeys.length - 1]!;
|
|
1422
|
-
const newItemValue = store.getShadowValue(newItemKey);
|
|
1423
|
-
const fullSourceArray = store.getShadowValue(
|
|
1424
|
-
[thisKey, ...path].join('.')
|
|
1425
|
-
);
|
|
1426
|
-
|
|
1427
|
-
if (!newItemKey || newItemValue === undefined) return;
|
|
1428
|
-
|
|
1429
|
-
shadowMeta.mapWrappers.forEach((wrapper) => {
|
|
1430
|
-
let shouldRender = true;
|
|
1431
|
-
let insertPosition = -1;
|
|
1432
|
-
|
|
1433
|
-
// Check if wrapper has transforms
|
|
1434
|
-
if (wrapper.meta?.transforms && wrapper.meta.transforms.length > 0) {
|
|
1435
|
-
// Check if new item passes all filters
|
|
1436
|
-
for (const transform of wrapper.meta.transforms) {
|
|
1437
|
-
if (transform.type === 'filter') {
|
|
1438
|
-
if (!transform.fn(newItemValue, -1)) {
|
|
1439
|
-
shouldRender = false;
|
|
1440
|
-
break;
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
if (shouldRender) {
|
|
1446
|
-
// Get current valid keys by applying transforms
|
|
1447
|
-
const currentValidKeys = applyTransforms(
|
|
1448
|
-
thisKey,
|
|
1449
|
-
path,
|
|
1450
|
-
wrapper.meta.transforms
|
|
1451
|
-
);
|
|
1452
|
-
|
|
1453
|
-
// Find where to insert based on sort
|
|
1454
|
-
const sortTransform = wrapper.meta.transforms.find(
|
|
1455
|
-
(t: any) => t.type === 'sort'
|
|
1456
|
-
);
|
|
1457
|
-
if (sortTransform) {
|
|
1458
|
-
// Add new item to the list and sort to find position
|
|
1459
|
-
const allItems = currentValidKeys.map((key) => ({
|
|
1460
|
-
key,
|
|
1461
|
-
value: store.getShadowValue(key),
|
|
1462
|
-
}));
|
|
1463
|
-
|
|
1464
|
-
allItems.push({ key: newItemKey, value: newItemValue });
|
|
1465
|
-
allItems.sort((a, b) => sortTransform.fn(a.value, b.value));
|
|
1466
|
-
|
|
1467
|
-
insertPosition = allItems.findIndex(
|
|
1468
|
-
(item) => item.key === newItemKey
|
|
1469
|
-
);
|
|
1470
|
-
} else {
|
|
1471
|
-
// No sort, insert at end
|
|
1472
|
-
insertPosition = currentValidKeys.length;
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
} else {
|
|
1476
|
-
// No transforms, always render at end
|
|
1477
|
-
shouldRender = true;
|
|
1478
|
-
insertPosition = sourceArrayKeys.length - 1;
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
if (!shouldRender) {
|
|
1482
|
-
return; // Skip this wrapper, item doesn't pass filters
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
if (wrapper.containerRef && wrapper.containerRef.isConnected) {
|
|
1486
|
-
const itemElement = document.createElement('div');
|
|
1487
|
-
itemElement.setAttribute('data-item-path', newItemKey);
|
|
1488
|
-
|
|
1489
|
-
// Insert at correct position
|
|
1490
|
-
const children = Array.from(wrapper.containerRef.children);
|
|
1491
|
-
if (insertPosition >= 0 && insertPosition < children.length) {
|
|
1492
|
-
wrapper.containerRef.insertBefore(
|
|
1493
|
-
itemElement,
|
|
1494
|
-
children[insertPosition]!
|
|
1495
|
-
);
|
|
1496
|
-
} else {
|
|
1497
|
-
wrapper.containerRef.appendChild(itemElement);
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
const root = createRoot(itemElement);
|
|
1501
|
-
const componentId = uuidv4();
|
|
1502
|
-
const itemPath = newItemKey.split('.').slice(1);
|
|
1503
|
-
|
|
1504
|
-
const arraySetter = wrapper.rebuildStateShape({
|
|
1505
|
-
path: wrapper.path,
|
|
1506
|
-
currentState: fullSourceArray,
|
|
1507
|
-
componentId: wrapper.componentId,
|
|
1508
|
-
meta: wrapper.meta,
|
|
1509
|
-
});
|
|
1510
|
-
|
|
1511
|
-
root.render(
|
|
1512
|
-
createElement(MemoizedCogsItemWrapper, {
|
|
1513
|
-
stateKey: thisKey,
|
|
1514
|
-
itemComponentId: componentId,
|
|
1515
|
-
itemPath: itemPath,
|
|
1516
|
-
localIndex: insertPosition,
|
|
1517
|
-
arraySetter: arraySetter,
|
|
1518
|
-
rebuildStateShape: wrapper.rebuildStateShape,
|
|
1519
|
-
renderFn: wrapper.mapFn,
|
|
1520
|
-
})
|
|
1521
|
-
);
|
|
1522
|
-
}
|
|
1523
|
-
});
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
if (updateObj.updateType === 'cut') {
|
|
1528
|
-
const arrayPath = path.slice(0, -1);
|
|
1529
|
-
const arrayMeta = store.getShadowMetadata(thisKey, arrayPath);
|
|
1530
|
-
|
|
1531
|
-
if (arrayMeta?.mapWrappers && arrayMeta.mapWrappers.length > 0) {
|
|
1532
|
-
arrayMeta.mapWrappers.forEach((wrapper) => {
|
|
1533
|
-
if (wrapper.containerRef && wrapper.containerRef.isConnected) {
|
|
1534
|
-
const elementToRemove = wrapper.containerRef.querySelector(
|
|
1535
|
-
`[data-item-path="${fullPath}"]`
|
|
1536
|
-
);
|
|
1537
|
-
if (elementToRemove) {
|
|
1538
|
-
elementToRemove.remove();
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
});
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
const rootMeta = store.getShadowMetadata(thisKey, []);
|
|
1546
|
-
const notifiedComponents = new Set<string>();
|
|
1547
|
-
|
|
1548
|
-
if (!rootMeta?.components) {
|
|
1549
|
-
return;
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
// --- PASS 1: Notify specific subscribers based on update type ---
|
|
1553
|
-
|
|
1554
|
-
if (updateObj.updateType === 'update') {
|
|
1555
|
-
// --- Bubble-up Notification ---
|
|
1556
|
-
// When a nested property changes, notify components listening at that exact path,
|
|
1557
|
-
// and also "bubble up" to notify components listening on parent paths.
|
|
1558
|
-
// e.g., an update to `user.address.street` notifies listeners of `street`, `address`, and `user`.
|
|
1559
|
-
let currentPath = [...path]; // Create a mutable copy of the path
|
|
1560
|
-
|
|
1561
|
-
while (true) {
|
|
1562
|
-
const currentPathMeta = store.getShadowMetadata(thisKey, currentPath);
|
|
1563
|
-
|
|
1564
|
-
if (currentPathMeta?.pathComponents) {
|
|
1565
|
-
currentPathMeta.pathComponents.forEach((componentId) => {
|
|
1566
|
-
if (notifiedComponents.has(componentId)) {
|
|
1567
|
-
return; // Avoid sending redundant notifications
|
|
1568
|
-
}
|
|
1569
|
-
const component = rootMeta.components?.get(componentId);
|
|
1570
|
-
if (component) {
|
|
1571
|
-
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1572
|
-
? component.reactiveType
|
|
1573
|
-
: [component.reactiveType || 'component'];
|
|
1574
|
-
|
|
1575
|
-
// This notification logic applies to components that depend on object structures.
|
|
1576
|
-
if (!reactiveTypes.includes('none')) {
|
|
1577
|
-
component.forceUpdate();
|
|
1578
|
-
notifiedComponents.add(componentId);
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
if (currentPath.length === 0) {
|
|
1585
|
-
break; // We've reached the root, stop bubbling.
|
|
1586
|
-
}
|
|
1587
|
-
currentPath.pop(); // Go up one level for the next iteration.
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
// ADDITIONALLY, if the payload is an object, perform a deep-check and
|
|
1591
|
-
// notify any components that are subscribed to specific sub-paths that changed.
|
|
1592
|
-
if (
|
|
1593
|
-
payload &&
|
|
1594
|
-
typeof payload === 'object' &&
|
|
1595
|
-
!isArray(payload) &&
|
|
1596
|
-
nestedShadowValue &&
|
|
1597
|
-
typeof nestedShadowValue === 'object' &&
|
|
1598
|
-
!isArray(nestedShadowValue)
|
|
1599
|
-
) {
|
|
1600
|
-
// Get a list of dot-separated paths that have changed (e.g., ['name', 'address.city'])
|
|
1601
|
-
const changedSubPaths = getDifferences(payload, nestedShadowValue);
|
|
1602
|
-
|
|
1603
|
-
changedSubPaths.forEach((subPathString) => {
|
|
1604
|
-
const subPath = subPathString.split('.');
|
|
1605
|
-
const fullSubPath = [...path, ...subPath];
|
|
1606
|
-
|
|
1607
|
-
// Get the metadata (and subscribers) for this specific nested path
|
|
1608
|
-
const subPathMeta = store.getShadowMetadata(thisKey, fullSubPath);
|
|
1609
|
-
if (subPathMeta?.pathComponents) {
|
|
1610
|
-
subPathMeta.pathComponents.forEach((componentId) => {
|
|
1611
|
-
// Avoid sending a redundant update
|
|
1612
|
-
if (notifiedComponents.has(componentId)) {
|
|
1613
|
-
return;
|
|
1614
|
-
}
|
|
1615
|
-
const component = rootMeta.components?.get(componentId);
|
|
1616
|
-
if (component) {
|
|
1617
|
-
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1618
|
-
? component.reactiveType
|
|
1619
|
-
: [component.reactiveType || 'component'];
|
|
1620
|
-
|
|
1621
|
-
if (!reactiveTypes.includes('none')) {
|
|
1622
|
-
component.forceUpdate();
|
|
1623
|
-
notifiedComponents.add(componentId);
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
});
|
|
1629
|
-
}
|
|
1630
|
-
} else if (
|
|
1631
|
-
updateObj.updateType === 'insert' ||
|
|
1632
|
-
updateObj.updateType === 'cut'
|
|
1633
|
-
) {
|
|
1634
|
-
// For array structural changes, notify components listening to the parent array.
|
|
1635
|
-
const parentArrayPath =
|
|
1636
|
-
updateObj.updateType === 'insert' ? path : path.slice(0, -1);
|
|
1637
|
-
|
|
1638
|
-
const parentMeta = store.getShadowMetadata(thisKey, parentArrayPath);
|
|
1639
|
-
|
|
1640
|
-
// Handle signal updates for array length, etc.
|
|
1641
|
-
if (parentMeta?.signals && parentMeta.signals.length > 0) {
|
|
1642
|
-
const parentFullPath = [thisKey, ...parentArrayPath].join('.');
|
|
1643
|
-
const parentValue = store.getShadowValue(parentFullPath);
|
|
1644
|
-
|
|
1645
|
-
parentMeta.signals.forEach(({ parentId, position, effect }) => {
|
|
1646
|
-
const parent = document.querySelector(
|
|
1647
|
-
`[data-parent-id="${parentId}"]`
|
|
1648
|
-
);
|
|
1649
|
-
if (parent) {
|
|
1650
|
-
const childNodes = Array.from(parent.childNodes);
|
|
1651
|
-
if (childNodes[position]) {
|
|
1652
|
-
let displayValue = parentValue;
|
|
1653
|
-
if (effect) {
|
|
1654
|
-
try {
|
|
1655
|
-
displayValue = new Function(
|
|
1656
|
-
'state',
|
|
1657
|
-
`return (${effect})(state)`
|
|
1658
|
-
)(parentValue);
|
|
1659
|
-
} catch (err) {
|
|
1660
|
-
console.error('Error evaluating effect function:', err);
|
|
1661
|
-
displayValue = parentValue;
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
if (
|
|
1666
|
-
displayValue !== null &&
|
|
1667
|
-
displayValue !== undefined &&
|
|
1668
|
-
typeof displayValue === 'object'
|
|
1669
|
-
) {
|
|
1670
|
-
displayValue = JSON.stringify(displayValue);
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
childNodes[position].textContent = String(displayValue ?? '');
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
});
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
// Notify components subscribed to the array itself.
|
|
1680
|
-
if (parentMeta?.pathComponents) {
|
|
1681
|
-
parentMeta.pathComponents.forEach((componentId) => {
|
|
1682
|
-
if (!notifiedComponents.has(componentId)) {
|
|
1683
|
-
const component = rootMeta.components?.get(componentId);
|
|
1684
|
-
if (component) {
|
|
1685
|
-
component.forceUpdate();
|
|
1686
|
-
notifiedComponents.add(componentId);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
});
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
rootMeta.components.forEach((component, componentId) => {
|
|
1694
|
-
if (notifiedComponents.has(componentId)) {
|
|
1695
|
-
return;
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
const reactiveTypes = Array.isArray(component.reactiveType)
|
|
1699
|
-
? component.reactiveType
|
|
1700
|
-
: [component.reactiveType || 'component'];
|
|
1701
|
-
|
|
1702
|
-
if (reactiveTypes.includes('all')) {
|
|
1703
|
-
component.forceUpdate();
|
|
1704
|
-
notifiedComponents.add(componentId);
|
|
1705
|
-
return;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
if (reactiveTypes.includes('deps')) {
|
|
1709
|
-
if (component.depsFunction) {
|
|
1710
|
-
const currentState = store.getShadowValue(thisKey);
|
|
1711
|
-
const newDeps = component.depsFunction(currentState);
|
|
1712
|
-
let shouldUpdate = false;
|
|
1713
|
-
|
|
1714
|
-
if (newDeps === true) {
|
|
1715
|
-
shouldUpdate = true;
|
|
1716
|
-
} else if (Array.isArray(newDeps)) {
|
|
1717
|
-
if (!isDeepEqual(component.prevDeps, newDeps)) {
|
|
1718
|
-
component.prevDeps = newDeps;
|
|
1719
|
-
shouldUpdate = true;
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
if (shouldUpdate) {
|
|
1724
|
-
component.forceUpdate();
|
|
1725
|
-
notifiedComponents.add(componentId);
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
});
|
|
1730
|
-
notifiedComponents.clear();
|
|
1731
|
-
|
|
1732
|
-
addStateLog(thisKey, newUpdate);
|
|
1733
|
-
|
|
1734
|
-
saveToLocalStorage(
|
|
1735
|
-
payload,
|
|
1736
|
-
thisKey,
|
|
1737
|
-
latestInitialOptionsRef.current,
|
|
1738
|
-
sessionId
|
|
1739
|
-
);
|
|
1740
|
-
|
|
1741
|
-
if (latestInitialOptionsRef.current?.middleware) {
|
|
1742
|
-
latestInitialOptionsRef.current!.middleware({
|
|
1743
|
-
update: newUpdate,
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1746
|
-
};
|
|
1656
|
+
const effectiveSetState = createEffectiveSetState(
|
|
1657
|
+
thisKey,
|
|
1658
|
+
syncApiRef,
|
|
1659
|
+
sessionId,
|
|
1660
|
+
latestInitialOptionsRef
|
|
1661
|
+
);
|
|
1747
1662
|
|
|
1748
1663
|
if (!getGlobalStore.getState().initialStateGlobal[thisKey]) {
|
|
1749
1664
|
updateInitialStateGlobal(thisKey, stateObject);
|
|
@@ -1773,30 +1688,15 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1773
1688
|
return updaterFinal;
|
|
1774
1689
|
}
|
|
1775
1690
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
*
|
|
1782
|
-
* - `stateFilter` populates this with only the IDs of items that passed the filter.
|
|
1783
|
-
* - `stateSort` reorders this list to match the new sort order.
|
|
1784
|
-
* - All subsequent chained operations (like `.get()`, `.index()`, or `.cut()`)
|
|
1785
|
-
* MUST consult this list first to know which items they apply to and in what order.
|
|
1786
|
-
*/
|
|
1787
|
-
validIds?: string[];
|
|
1788
|
-
|
|
1789
|
-
/**
|
|
1790
|
-
* An array of the actual filter functions that have been applied in a chain.
|
|
1791
|
-
* This is primarily used by reactive renderers like `$stateMap` to make predictions.
|
|
1792
|
-
*
|
|
1793
|
-
* For example, when a new item is inserted into the original source array, a
|
|
1794
|
-
* `$stateMap` renderer on a filtered view can use these functions to test if the
|
|
1795
|
-
* newly inserted item should be dynamically rendered in its view.
|
|
1796
|
-
*/
|
|
1691
|
+
type MetaData = {
|
|
1692
|
+
// Map array paths to their filtered/sorted ID order
|
|
1693
|
+
arrayViews?: {
|
|
1694
|
+
[arrayPath: string]: string[]; // e.g. { "todos": ["id:xxx", "id:yyy"], "todos.id:xxx.subtasks": ["id:aaa"] }
|
|
1695
|
+
};
|
|
1797
1696
|
transforms?: Array<{
|
|
1798
1697
|
type: 'filter' | 'sort';
|
|
1799
1698
|
fn: Function;
|
|
1699
|
+
path: string[]; // Which array this transform applies to
|
|
1800
1700
|
}>;
|
|
1801
1701
|
};
|
|
1802
1702
|
|
|
@@ -1811,35 +1711,40 @@ function hashTransforms(transforms: any[]) {
|
|
|
1811
1711
|
)
|
|
1812
1712
|
.join('');
|
|
1813
1713
|
}
|
|
1714
|
+
|
|
1814
1715
|
const applyTransforms = (
|
|
1815
1716
|
stateKey: string,
|
|
1816
1717
|
path: string[],
|
|
1817
1718
|
transforms?: Array<{ type: 'filter' | 'sort'; fn: Function }>
|
|
1818
1719
|
): string[] => {
|
|
1819
|
-
let arrayKeys
|
|
1820
|
-
|
|
1821
|
-
[];
|
|
1822
|
-
|
|
1720
|
+
let ids = getShadowMetadata(stateKey, path)?.arrayKeys || [];
|
|
1721
|
+
console.log('ids', ids);
|
|
1823
1722
|
if (!transforms || transforms.length === 0) {
|
|
1824
|
-
return
|
|
1723
|
+
return ids;
|
|
1825
1724
|
}
|
|
1826
1725
|
|
|
1827
|
-
|
|
1828
|
-
key,
|
|
1829
|
-
value: getGlobalStore.getState().getShadowValue(key),
|
|
1830
|
-
}));
|
|
1831
|
-
|
|
1726
|
+
// Apply each transform using just IDs
|
|
1832
1727
|
for (const transform of transforms) {
|
|
1833
1728
|
if (transform.type === 'filter') {
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1729
|
+
const filtered: any[] = [];
|
|
1730
|
+
ids.forEach((id, index) => {
|
|
1731
|
+
const value = getShadowValue(stateKey, [...path, id]);
|
|
1732
|
+
|
|
1733
|
+
if (transform.fn(value, index)) {
|
|
1734
|
+
filtered.push(id);
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1737
|
+
ids = filtered;
|
|
1837
1738
|
} else if (transform.type === 'sort') {
|
|
1838
|
-
|
|
1739
|
+
ids.sort((a, b) => {
|
|
1740
|
+
const aValue = getShadowValue(stateKey, [...path, a]);
|
|
1741
|
+
const bValue = getShadowValue(stateKey, [...path, b]);
|
|
1742
|
+
return transform.fn(aValue, bValue);
|
|
1743
|
+
});
|
|
1839
1744
|
}
|
|
1840
1745
|
}
|
|
1841
1746
|
|
|
1842
|
-
return
|
|
1747
|
+
return ids;
|
|
1843
1748
|
};
|
|
1844
1749
|
const registerComponentDependency = (
|
|
1845
1750
|
stateKey: string,
|
|
@@ -1847,7 +1752,6 @@ const registerComponentDependency = (
|
|
|
1847
1752
|
dependencyPath: string[]
|
|
1848
1753
|
) => {
|
|
1849
1754
|
const fullComponentId = `${stateKey}////${componentId}`;
|
|
1850
|
-
const { addPathComponent, getShadowMetadata } = getGlobalStore.getState();
|
|
1851
1755
|
|
|
1852
1756
|
const rootMeta = getShadowMetadata(stateKey, []);
|
|
1853
1757
|
const component = rootMeta?.components?.get(fullComponentId);
|
|
@@ -1911,6 +1815,28 @@ const notifySelectionComponents = (
|
|
|
1911
1815
|
}
|
|
1912
1816
|
}
|
|
1913
1817
|
};
|
|
1818
|
+
function getScopedData(stateKey: string, path: string[], meta?: MetaData) {
|
|
1819
|
+
const shadowMeta = getShadowMetadata(stateKey, path);
|
|
1820
|
+
const arrayPathKey = path.join('.');
|
|
1821
|
+
const arrayKeys = meta?.arrayViews?.[arrayPathKey];
|
|
1822
|
+
|
|
1823
|
+
// FIX: If the derived view is empty, return an empty array directly.
|
|
1824
|
+
if (Array.isArray(arrayKeys) && arrayKeys.length === 0) {
|
|
1825
|
+
return {
|
|
1826
|
+
shadowMeta,
|
|
1827
|
+
value: [],
|
|
1828
|
+
arrayKeys: shadowMeta?.arrayKeys,
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
const value = getShadowValue(stateKey, path, arrayKeys);
|
|
1833
|
+
|
|
1834
|
+
return {
|
|
1835
|
+
shadowMeta,
|
|
1836
|
+
value,
|
|
1837
|
+
arrayKeys: shadowMeta?.arrayKeys,
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1914
1840
|
|
|
1915
1841
|
function createProxyHandler<T>(
|
|
1916
1842
|
stateKey: string,
|
|
@@ -1921,7 +1847,57 @@ function createProxyHandler<T>(
|
|
|
1921
1847
|
const proxyCache = new Map<string, any>();
|
|
1922
1848
|
let stateVersion = 0;
|
|
1923
1849
|
|
|
1924
|
-
|
|
1850
|
+
const methodNames = new Set([
|
|
1851
|
+
'sync',
|
|
1852
|
+
'getStatus',
|
|
1853
|
+
'removeStorage',
|
|
1854
|
+
'showValidationErrors',
|
|
1855
|
+
'getSelected',
|
|
1856
|
+
'getSelectedIndex',
|
|
1857
|
+
'clearSelected',
|
|
1858
|
+
'useVirtualView',
|
|
1859
|
+
'stateMap',
|
|
1860
|
+
'$stateMap',
|
|
1861
|
+
'stateFind',
|
|
1862
|
+
'stateFilter',
|
|
1863
|
+
'stateSort',
|
|
1864
|
+
'stream',
|
|
1865
|
+
'stateList',
|
|
1866
|
+
'stateFlattenOn',
|
|
1867
|
+
'index',
|
|
1868
|
+
'last',
|
|
1869
|
+
'insert',
|
|
1870
|
+
'uniqueInsert',
|
|
1871
|
+
'cut',
|
|
1872
|
+
'cutSelected',
|
|
1873
|
+
'cutByValue',
|
|
1874
|
+
'toggleByValue',
|
|
1875
|
+
'findWith',
|
|
1876
|
+
'cutThis',
|
|
1877
|
+
'get',
|
|
1878
|
+
'getState',
|
|
1879
|
+
'$derive',
|
|
1880
|
+
'$get',
|
|
1881
|
+
'lastSynced',
|
|
1882
|
+
'getLocalStorage',
|
|
1883
|
+
'isSelected',
|
|
1884
|
+
'setSelected',
|
|
1885
|
+
'toggleSelected',
|
|
1886
|
+
'_componentId',
|
|
1887
|
+
'addZodValidation',
|
|
1888
|
+
'clearZodValidation',
|
|
1889
|
+
'applyJsonPatch',
|
|
1890
|
+
'getComponents',
|
|
1891
|
+
'getAllFormRefs',
|
|
1892
|
+
'getFormRef',
|
|
1893
|
+
'validationWrapper',
|
|
1894
|
+
'_stateKey',
|
|
1895
|
+
'_path',
|
|
1896
|
+
'update',
|
|
1897
|
+
'toggle',
|
|
1898
|
+
'formElement',
|
|
1899
|
+
// Add ANY other method names here
|
|
1900
|
+
]);
|
|
1925
1901
|
|
|
1926
1902
|
function rebuildStateShape({
|
|
1927
1903
|
path = [],
|
|
@@ -1933,64 +1909,34 @@ function createProxyHandler<T>(
|
|
|
1933
1909
|
meta?: MetaData;
|
|
1934
1910
|
}): any {
|
|
1935
1911
|
const derivationSignature = meta
|
|
1936
|
-
? JSON.stringify(meta.
|
|
1912
|
+
? JSON.stringify(meta.arrayViews || meta.transforms)
|
|
1937
1913
|
: '';
|
|
1938
1914
|
const cacheKey = path.join('.') + ':' + derivationSignature;
|
|
1939
|
-
console.log('PROXY CACHE KEY ', cacheKey);
|
|
1940
1915
|
if (proxyCache.has(cacheKey)) {
|
|
1941
|
-
console.log('PROXY CACHE HIT');
|
|
1942
1916
|
return proxyCache.get(cacheKey);
|
|
1943
1917
|
}
|
|
1944
1918
|
const stateKeyPathKey = [stateKey, ...path].join('.');
|
|
1945
1919
|
|
|
1946
|
-
type CallableStateObject<T> = {
|
|
1947
|
-
(): T;
|
|
1948
|
-
} & {
|
|
1949
|
-
[key: string]: any;
|
|
1950
|
-
};
|
|
1951
|
-
|
|
1952
|
-
const baseFunction = function () {
|
|
1953
|
-
return getGlobalStore().getShadowValue(stateKey, path);
|
|
1954
|
-
} as unknown as CallableStateObject<T>;
|
|
1955
|
-
|
|
1956
1920
|
// We attach baseObj properties *inside* the get trap now to avoid recursion
|
|
1957
1921
|
// This is a placeholder for the proxy.
|
|
1958
1922
|
|
|
1959
1923
|
const handler = {
|
|
1960
1924
|
get(target: any, prop: string) {
|
|
1925
|
+
if (path.length === 0 && prop in rootLevelMethods) {
|
|
1926
|
+
return rootLevelMethods[prop as keyof typeof rootLevelMethods];
|
|
1927
|
+
}
|
|
1928
|
+
if (!methodNames.has(prop)) {
|
|
1929
|
+
const nextPath = [...path, prop];
|
|
1930
|
+
return rebuildStateShape({
|
|
1931
|
+
path: nextPath,
|
|
1932
|
+
componentId: componentId!,
|
|
1933
|
+
meta,
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1961
1936
|
if (prop === '_rebuildStateShape') {
|
|
1962
1937
|
return rebuildStateShape;
|
|
1963
1938
|
}
|
|
1964
|
-
const baseObjProps = Object.getOwnPropertyNames(baseObj);
|
|
1965
|
-
if (baseObjProps.includes(prop) && path.length === 0) {
|
|
1966
|
-
return (baseObj as any)[prop];
|
|
1967
|
-
}
|
|
1968
|
-
// ^--------- END OF FIX ---------^
|
|
1969
|
-
|
|
1970
|
-
if (prop === 'getDifferences') {
|
|
1971
|
-
return () => {
|
|
1972
|
-
const shadowMeta = getGlobalStore
|
|
1973
|
-
.getState()
|
|
1974
|
-
.getShadowMetadata(stateKey, []);
|
|
1975
|
-
const currentState = getGlobalStore
|
|
1976
|
-
.getState()
|
|
1977
|
-
.getShadowValue(stateKey);
|
|
1978
1939
|
|
|
1979
|
-
// Use the appropriate base state for comparison
|
|
1980
|
-
let baseState;
|
|
1981
|
-
if (
|
|
1982
|
-
shadowMeta?.stateSource === 'server' &&
|
|
1983
|
-
shadowMeta.baseServerState
|
|
1984
|
-
) {
|
|
1985
|
-
baseState = shadowMeta.baseServerState;
|
|
1986
|
-
} else {
|
|
1987
|
-
baseState =
|
|
1988
|
-
getGlobalStore.getState().initialStateGlobal[stateKey];
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
|
-
return getDifferences(currentState, baseState);
|
|
1992
|
-
};
|
|
1993
|
-
}
|
|
1994
1940
|
if (prop === 'sync' && path.length === 0) {
|
|
1995
1941
|
return async function () {
|
|
1996
1942
|
const options = getGlobalStore
|
|
@@ -2031,7 +1977,7 @@ function createProxyHandler<T>(
|
|
|
2031
1977
|
const shadowMeta = getGlobalStore
|
|
2032
1978
|
.getState()
|
|
2033
1979
|
.getShadowMetadata(stateKey, []);
|
|
2034
|
-
|
|
1980
|
+
setShadowMetadata(stateKey, [], {
|
|
2035
1981
|
...shadowMeta,
|
|
2036
1982
|
isDirty: false,
|
|
2037
1983
|
lastServerSync: Date.now(),
|
|
@@ -2055,56 +2001,46 @@ function createProxyHandler<T>(
|
|
|
2055
2001
|
// Fixed getStatus function in createProxyHandler
|
|
2056
2002
|
if (prop === '_status' || prop === 'getStatus') {
|
|
2057
2003
|
const getStatusFunc = () => {
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
.getShadowMetadata(stateKey, path);
|
|
2061
|
-
const value = getGlobalStore
|
|
2062
|
-
.getState()
|
|
2063
|
-
.getShadowValue(stateKeyPathKey);
|
|
2004
|
+
// ✅ Use the optimized helper to get all data in one efficient call
|
|
2005
|
+
const { shadowMeta, value } = getScopedData(stateKey, path, meta);
|
|
2064
2006
|
|
|
2065
|
-
// Priority 1: Explicitly dirty items
|
|
2007
|
+
// Priority 1: Explicitly dirty items. This is the most important status.
|
|
2066
2008
|
if (shadowMeta?.isDirty === true) {
|
|
2067
2009
|
return 'dirty';
|
|
2068
2010
|
}
|
|
2069
2011
|
|
|
2070
|
-
// Priority 2:
|
|
2071
|
-
if
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2012
|
+
// ✅ Priority 2: Synced items. This condition is now cleaner.
|
|
2013
|
+
// An item is considered synced if it came from the server OR was explicitly
|
|
2014
|
+
// marked as not dirty (isDirty: false), covering all sync-related cases.
|
|
2015
|
+
if (
|
|
2016
|
+
shadowMeta?.stateSource === 'server' ||
|
|
2017
|
+
shadowMeta?.isDirty === false
|
|
2018
|
+
) {
|
|
2077
2019
|
return 'synced';
|
|
2078
2020
|
}
|
|
2079
2021
|
|
|
2080
|
-
// Priority
|
|
2022
|
+
// Priority 3: Items restored from localStorage.
|
|
2081
2023
|
if (shadowMeta?.stateSource === 'localStorage') {
|
|
2082
2024
|
return 'restored';
|
|
2083
2025
|
}
|
|
2084
2026
|
|
|
2085
|
-
// Priority
|
|
2027
|
+
// Priority 4: Items from default/initial state.
|
|
2086
2028
|
if (shadowMeta?.stateSource === 'default') {
|
|
2087
2029
|
return 'fresh';
|
|
2088
2030
|
}
|
|
2089
2031
|
|
|
2090
|
-
//
|
|
2091
|
-
// Look up the tree to see if parent has server source
|
|
2092
|
-
const rootMeta = getGlobalStore
|
|
2093
|
-
.getState()
|
|
2094
|
-
.getShadowMetadata(stateKey, []);
|
|
2095
|
-
if (rootMeta?.stateSource === 'server' && !shadowMeta?.isDirty) {
|
|
2096
|
-
return 'synced';
|
|
2097
|
-
}
|
|
2032
|
+
// ✅ REMOVED the redundant "root" check. The item's own `stateSource` is sufficient.
|
|
2098
2033
|
|
|
2099
|
-
// Priority
|
|
2034
|
+
// Priority 5: A value exists but has no metadata. This is a fallback.
|
|
2100
2035
|
if (value !== undefined && !shadowMeta) {
|
|
2101
2036
|
return 'fresh';
|
|
2102
2037
|
}
|
|
2103
2038
|
|
|
2104
|
-
// Fallback
|
|
2039
|
+
// Fallback if no other condition is met.
|
|
2105
2040
|
return 'unknown';
|
|
2106
2041
|
};
|
|
2107
2042
|
|
|
2043
|
+
// This part remains the same
|
|
2108
2044
|
return prop === '_status' ? getStatusFunc() : getStatusFunc;
|
|
2109
2045
|
}
|
|
2110
2046
|
if (prop === 'removeStorage') {
|
|
@@ -2121,14 +2057,15 @@ function createProxyHandler<T>(
|
|
|
2121
2057
|
}
|
|
2122
2058
|
if (prop === 'showValidationErrors') {
|
|
2123
2059
|
return () => {
|
|
2124
|
-
const
|
|
2125
|
-
.getState()
|
|
2126
|
-
.getShadowMetadata(stateKey, path);
|
|
2060
|
+
const { shadowMeta } = getScopedData(stateKey, path, meta);
|
|
2127
2061
|
if (
|
|
2128
|
-
|
|
2129
|
-
|
|
2062
|
+
shadowMeta?.validation?.status === 'INVALID' &&
|
|
2063
|
+
shadowMeta.validation.errors.length > 0
|
|
2130
2064
|
) {
|
|
2131
|
-
|
|
2065
|
+
// Return only error-severity messages (not warnings)
|
|
2066
|
+
return shadowMeta.validation.errors
|
|
2067
|
+
.filter((err) => err.severity === 'error')
|
|
2068
|
+
.map((err) => err.message);
|
|
2132
2069
|
}
|
|
2133
2070
|
return [];
|
|
2134
2071
|
};
|
|
@@ -2136,55 +2073,77 @@ function createProxyHandler<T>(
|
|
|
2136
2073
|
|
|
2137
2074
|
if (prop === 'getSelected') {
|
|
2138
2075
|
return () => {
|
|
2139
|
-
const
|
|
2076
|
+
const arrayKey = [stateKey, ...path].join('.');
|
|
2140
2077
|
registerComponentDependency(stateKey, componentId, [
|
|
2141
2078
|
...path,
|
|
2142
2079
|
'getSelected',
|
|
2143
2080
|
]);
|
|
2144
2081
|
|
|
2145
|
-
const
|
|
2146
|
-
|
|
2147
|
-
|
|
2082
|
+
const selectedItemKey = getGlobalStore
|
|
2083
|
+
.getState()
|
|
2084
|
+
.selectedIndicesMap.get(arrayKey);
|
|
2085
|
+
if (!selectedItemKey) {
|
|
2148
2086
|
return undefined;
|
|
2149
2087
|
}
|
|
2150
2088
|
|
|
2151
|
-
const
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
return undefined;
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2089
|
+
const viewKey = path.join('.');
|
|
2090
|
+
const currentViewIds = meta?.arrayViews?.[viewKey];
|
|
2091
|
+
const selectedItemId = selectedItemKey.split('.').pop();
|
|
2157
2092
|
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2093
|
+
// FIX: Only return the selected item if it exists in the current filtered/sorted view.
|
|
2094
|
+
if (currentViewIds && !currentViewIds.includes(selectedItemId!)) {
|
|
2095
|
+
return undefined;
|
|
2096
|
+
}
|
|
2161
2097
|
|
|
2162
|
-
|
|
2098
|
+
const value = getShadowValue(
|
|
2099
|
+
stateKey,
|
|
2100
|
+
selectedItemKey.split('.').slice(1)
|
|
2101
|
+
);
|
|
2102
|
+
if (value === undefined) {
|
|
2163
2103
|
return undefined;
|
|
2164
2104
|
}
|
|
2165
2105
|
|
|
2166
2106
|
return rebuildStateShape({
|
|
2167
2107
|
path: selectedItemKey.split('.').slice(1) as string[],
|
|
2168
2108
|
componentId: componentId!,
|
|
2109
|
+
meta,
|
|
2169
2110
|
});
|
|
2170
2111
|
};
|
|
2171
2112
|
}
|
|
2172
2113
|
if (prop === 'getSelectedIndex') {
|
|
2173
2114
|
return () => {
|
|
2174
|
-
|
|
2115
|
+
// Key for the array in the global selection map (e.g., "myState.products")
|
|
2116
|
+
const arrayKey = stateKey + '.' + path.join('.');
|
|
2117
|
+
// Key for this specific view in the meta object (e.g., "products")
|
|
2118
|
+
const viewKey = path.join('.');
|
|
2119
|
+
|
|
2120
|
+
// Get the full path of the selected item (e.g., "myState.products.id:abc")
|
|
2121
|
+
const selectedItemKey = getGlobalStore
|
|
2175
2122
|
.getState()
|
|
2176
|
-
.
|
|
2177
|
-
stateKey + '.' + path.join('.'),
|
|
2178
|
-
meta?.validIds
|
|
2179
|
-
);
|
|
2123
|
+
.selectedIndicesMap.get(arrayKey);
|
|
2180
2124
|
|
|
2181
|
-
|
|
2125
|
+
if (!selectedItemKey) {
|
|
2126
|
+
return -1; // Nothing is selected for this array.
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// Get the list of item IDs for the current filtered/sorted view.
|
|
2130
|
+
const { keys: viewIds } = getArrayData(stateKey, path, meta);
|
|
2131
|
+
|
|
2132
|
+
if (!viewIds) {
|
|
2133
|
+
return -1; // Should not happen if it's an array, but a safe guard.
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
// FIX: Extract just the ID from the full selected item path.
|
|
2137
|
+
const selectedId = selectedItemKey.split('.').pop();
|
|
2138
|
+
|
|
2139
|
+
// Return the index of that ID within the current view's list of IDs.
|
|
2140
|
+
return (viewIds as string[]).indexOf(selectedId as string);
|
|
2182
2141
|
};
|
|
2183
2142
|
}
|
|
2184
2143
|
if (prop === 'clearSelected') {
|
|
2185
2144
|
notifySelectionComponents(stateKey, path);
|
|
2186
2145
|
return () => {
|
|
2187
|
-
|
|
2146
|
+
clearSelectedIndex({
|
|
2188
2147
|
arrayKey: stateKey + '.' + path.join('.'),
|
|
2189
2148
|
});
|
|
2190
2149
|
};
|
|
@@ -2238,9 +2197,7 @@ function createProxyHandler<T>(
|
|
|
2238
2197
|
});
|
|
2239
2198
|
}, [rerender, stickToBottom]);
|
|
2240
2199
|
|
|
2241
|
-
const arrayKeys =
|
|
2242
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2243
|
-
?.arrayKeys || [];
|
|
2200
|
+
const { arrayKeys = [] } = getScopedData(stateKey, path, meta);
|
|
2244
2201
|
|
|
2245
2202
|
// Calculate total height and offsets
|
|
2246
2203
|
const { totalHeight, itemOffsets } = useMemo(() => {
|
|
@@ -2518,9 +2475,7 @@ function createProxyHandler<T>(
|
|
|
2518
2475
|
// Create virtual state
|
|
2519
2476
|
const virtualState = useMemo(() => {
|
|
2520
2477
|
const store = getGlobalStore.getState();
|
|
2521
|
-
const sourceArray = store.getShadowValue(
|
|
2522
|
-
[stateKey, ...path].join('.')
|
|
2523
|
-
) as any[];
|
|
2478
|
+
const sourceArray = store.getShadowValue(stateKey, path) as any[];
|
|
2524
2479
|
const currentKeys =
|
|
2525
2480
|
store.getShadowMetadata(stateKey, path)?.arrayKeys || [];
|
|
2526
2481
|
|
|
@@ -2532,11 +2487,11 @@ function createProxyHandler<T>(
|
|
|
2532
2487
|
range.startIndex,
|
|
2533
2488
|
range.endIndex + 1
|
|
2534
2489
|
);
|
|
2535
|
-
|
|
2490
|
+
const arrayPath = path.length > 0 ? path.join('.') : 'root';
|
|
2536
2491
|
return rebuildStateShape({
|
|
2537
2492
|
path,
|
|
2538
2493
|
componentId: componentId!,
|
|
2539
|
-
meta: { ...meta,
|
|
2494
|
+
meta: { ...meta, arrayViews: { [arrayPath]: slicedIds } },
|
|
2540
2495
|
});
|
|
2541
2496
|
}, [range.startIndex, range.endIndex, arrayKeys.length]);
|
|
2542
2497
|
|
|
@@ -2583,150 +2538,84 @@ function createProxyHandler<T>(
|
|
|
2583
2538
|
}
|
|
2584
2539
|
if (prop === 'stateMap') {
|
|
2585
2540
|
return (
|
|
2586
|
-
callbackfn: (
|
|
2587
|
-
setter: any,
|
|
2588
|
-
index: number,
|
|
2589
|
-
|
|
2590
|
-
arraySetter: any
|
|
2591
|
-
) => void
|
|
2541
|
+
callbackfn: (setter: any, index: number, arraySetter: any) => void
|
|
2592
2542
|
) => {
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2543
|
+
// FIX: Use getArrayData to reliably get both the value and the keys of the current view.
|
|
2544
|
+
const { value: shadowValue, keys: arrayKeys } = getArrayData(
|
|
2545
|
+
stateKey,
|
|
2546
|
+
path,
|
|
2547
|
+
meta
|
|
2597
2548
|
);
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
// stateKeyPathKey
|
|
2602
|
-
// );
|
|
2603
|
-
// setArrayKeys(
|
|
2604
|
-
// getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2605
|
-
// );
|
|
2606
|
-
// });
|
|
2607
|
-
|
|
2608
|
-
const shadowValue = getGlobalStore
|
|
2609
|
-
.getState()
|
|
2610
|
-
.getShadowValue(stateKeyPathKey, meta?.validIds) as any[];
|
|
2611
|
-
if (!arrayKeys) {
|
|
2612
|
-
throw new Error('No array keys found for mapping');
|
|
2549
|
+
|
|
2550
|
+
if (!arrayKeys || !Array.isArray(shadowValue)) {
|
|
2551
|
+
return []; // It's valid to map over an empty array.
|
|
2613
2552
|
}
|
|
2553
|
+
|
|
2614
2554
|
const arraySetter = rebuildStateShape({
|
|
2615
2555
|
path,
|
|
2616
2556
|
componentId: componentId!,
|
|
2617
2557
|
meta,
|
|
2618
|
-
});
|
|
2619
|
-
|
|
2620
|
-
return shadowValue.map((item, index) => {
|
|
2621
|
-
const itemPath = arrayKeys[index]?.split('.').slice(1);
|
|
2622
|
-
const itemSetter = rebuildStateShape({
|
|
2623
|
-
path: itemPath as any,
|
|
2624
|
-
componentId: componentId!,
|
|
2625
|
-
meta,
|
|
2626
|
-
});
|
|
2627
|
-
|
|
2628
|
-
return callbackfn(
|
|
2629
|
-
itemSetter,
|
|
2630
|
-
index,
|
|
2631
|
-
|
|
2632
|
-
arraySetter
|
|
2633
|
-
);
|
|
2634
|
-
});
|
|
2635
|
-
};
|
|
2636
|
-
}
|
|
2637
|
-
|
|
2638
|
-
if (prop === '$stateMap') {
|
|
2639
|
-
return (callbackfn: any) =>
|
|
2640
|
-
createElement(SignalMapRenderer, {
|
|
2641
|
-
proxy: {
|
|
2642
|
-
_stateKey: stateKey,
|
|
2643
|
-
_path: path,
|
|
2644
|
-
_mapFn: callbackfn,
|
|
2645
|
-
_meta: meta,
|
|
2646
|
-
},
|
|
2647
|
-
rebuildStateShape,
|
|
2648
|
-
});
|
|
2649
|
-
} // In createProxyHandler -> handler -> get -> if (Array.isArray(currentState))
|
|
2650
|
-
|
|
2651
|
-
if (prop === 'stateFind') {
|
|
2652
|
-
return (
|
|
2653
|
-
callbackfn: (value: any, index: number) => boolean
|
|
2654
|
-
): StateObject<any> | undefined => {
|
|
2655
|
-
// 1. Use the correct set of keys: filtered/sorted from meta, or all keys from the store.
|
|
2656
|
-
const arrayKeys =
|
|
2657
|
-
meta?.validIds ??
|
|
2658
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2659
|
-
?.arrayKeys;
|
|
2660
|
-
|
|
2661
|
-
if (!arrayKeys) {
|
|
2662
|
-
return undefined;
|
|
2663
|
-
}
|
|
2664
|
-
|
|
2665
|
-
// 2. Iterate through the keys, get the value for each, and run the callback.
|
|
2666
|
-
for (let i = 0; i < arrayKeys.length; i++) {
|
|
2667
|
-
const itemKey = arrayKeys[i];
|
|
2668
|
-
if (!itemKey) continue; // Safety check
|
|
2558
|
+
});
|
|
2669
2559
|
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2560
|
+
return shadowValue.map((_item, index) => {
|
|
2561
|
+
const itemKey = arrayKeys[index];
|
|
2562
|
+
if (!itemKey) return undefined;
|
|
2673
2563
|
|
|
2674
|
-
//
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
const itemPath = itemKey.split('.').slice(1);
|
|
2564
|
+
// FIX: Construct the correct path to the item in the original store.
|
|
2565
|
+
// The path is the array's path plus the specific item's unique key.
|
|
2566
|
+
const itemPath = [...path, itemKey];
|
|
2678
2567
|
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
});
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2568
|
+
const itemSetter = rebuildStateShape({
|
|
2569
|
+
path: itemPath, // This now correctly points to the item in the shadow store.
|
|
2570
|
+
componentId: componentId!,
|
|
2571
|
+
meta,
|
|
2572
|
+
});
|
|
2687
2573
|
|
|
2688
|
-
|
|
2689
|
-
|
|
2574
|
+
return callbackfn(itemSetter, index, arraySetter);
|
|
2575
|
+
});
|
|
2690
2576
|
};
|
|
2691
2577
|
}
|
|
2578
|
+
|
|
2692
2579
|
if (prop === 'stateFilter') {
|
|
2693
2580
|
return (callbackfn: (value: any, index: number) => boolean) => {
|
|
2694
|
-
const
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2581
|
+
const arrayPathKey = path.length > 0 ? path.join('.') : 'root';
|
|
2582
|
+
|
|
2583
|
+
// ✅ FIX: Get keys from `getArrayData` which correctly resolves them from meta or the full list.
|
|
2584
|
+
const { keys: currentViewIds, value: array } = getArrayData(
|
|
2585
|
+
stateKey,
|
|
2586
|
+
path,
|
|
2587
|
+
meta
|
|
2588
|
+
);
|
|
2702
2589
|
|
|
2703
|
-
if (!
|
|
2704
|
-
throw new Error('
|
|
2590
|
+
if (!Array.isArray(array)) {
|
|
2591
|
+
throw new Error('stateFilter can only be used on arrays');
|
|
2705
2592
|
}
|
|
2706
2593
|
|
|
2707
|
-
|
|
2708
|
-
const
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2594
|
+
// Filter the array and collect the IDs of the items that pass
|
|
2595
|
+
const filteredIds: string[] = [];
|
|
2596
|
+
array.forEach((item, index) => {
|
|
2597
|
+
if (callbackfn(item, index)) {
|
|
2598
|
+
// currentViewIds[index] is the original ID before filtering
|
|
2599
|
+
const id = currentViewIds[index];
|
|
2600
|
+
if (id) {
|
|
2601
|
+
filteredIds.push(id);
|
|
2714
2602
|
}
|
|
2715
|
-
return false;
|
|
2716
2603
|
}
|
|
2717
|
-
);
|
|
2604
|
+
});
|
|
2718
2605
|
|
|
2606
|
+
// The rest is the same...
|
|
2719
2607
|
return rebuildStateShape({
|
|
2720
2608
|
path,
|
|
2721
2609
|
componentId: componentId!,
|
|
2722
2610
|
meta: {
|
|
2723
|
-
|
|
2611
|
+
...meta,
|
|
2612
|
+
arrayViews: {
|
|
2613
|
+
...(meta?.arrayViews || {}),
|
|
2614
|
+
[arrayPathKey]: filteredIds,
|
|
2615
|
+
},
|
|
2724
2616
|
transforms: [
|
|
2725
2617
|
...(meta?.transforms || []),
|
|
2726
|
-
{
|
|
2727
|
-
type: 'filter',
|
|
2728
|
-
fn: callbackfn,
|
|
2729
|
-
},
|
|
2618
|
+
{ type: 'filter', fn: callbackfn, path },
|
|
2730
2619
|
],
|
|
2731
2620
|
},
|
|
2732
2621
|
});
|
|
@@ -2734,34 +2623,39 @@ function createProxyHandler<T>(
|
|
|
2734
2623
|
}
|
|
2735
2624
|
if (prop === 'stateSort') {
|
|
2736
2625
|
return (compareFn: (a: any, b: any) => number) => {
|
|
2737
|
-
const
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2626
|
+
const arrayPathKey = path.join('.');
|
|
2627
|
+
|
|
2628
|
+
// FIX: Use the more robust `getArrayData` which always correctly resolves the keys for a view.
|
|
2629
|
+
const { value: currentArray, keys: currentViewIds } = getArrayData(
|
|
2630
|
+
stateKey,
|
|
2631
|
+
path,
|
|
2632
|
+
meta
|
|
2633
|
+
);
|
|
2634
|
+
|
|
2635
|
+
if (!Array.isArray(currentArray) || !currentViewIds) {
|
|
2746
2636
|
throw new Error('No array keys found for sorting');
|
|
2747
2637
|
}
|
|
2748
|
-
|
|
2638
|
+
|
|
2639
|
+
// ... (rest of the function is the same and now works)
|
|
2640
|
+
const itemsWithIds = currentArray.map((item, index) => ({
|
|
2749
2641
|
item,
|
|
2750
|
-
key:
|
|
2642
|
+
key: currentViewIds[index],
|
|
2751
2643
|
}));
|
|
2752
|
-
|
|
2753
|
-
itemsWithIds
|
|
2754
|
-
.sort((a, b) => compareFn(a.item, b.item))
|
|
2755
|
-
.filter(Boolean);
|
|
2644
|
+
itemsWithIds.sort((a, b) => compareFn(a.item, b.item));
|
|
2645
|
+
const sortedIds = itemsWithIds.map((i) => i.key as string);
|
|
2756
2646
|
|
|
2757
2647
|
return rebuildStateShape({
|
|
2758
2648
|
path,
|
|
2759
2649
|
componentId: componentId!,
|
|
2760
2650
|
meta: {
|
|
2761
|
-
|
|
2651
|
+
...meta,
|
|
2652
|
+
arrayViews: {
|
|
2653
|
+
...(meta?.arrayViews || {}),
|
|
2654
|
+
[arrayPathKey]: sortedIds,
|
|
2655
|
+
},
|
|
2762
2656
|
transforms: [
|
|
2763
2657
|
...(meta?.transforms || []),
|
|
2764
|
-
{ type: 'sort', fn: compareFn },
|
|
2658
|
+
{ type: 'sort', fn: compareFn, path },
|
|
2765
2659
|
],
|
|
2766
2660
|
},
|
|
2767
2661
|
});
|
|
@@ -2835,12 +2729,11 @@ function createProxyHandler<T>(
|
|
|
2835
2729
|
}
|
|
2836
2730
|
|
|
2837
2731
|
const streamId = uuidv4();
|
|
2838
|
-
const currentMeta =
|
|
2839
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, path) || {};
|
|
2732
|
+
const currentMeta = getShadowMetadata(stateKey, path) || {};
|
|
2840
2733
|
const streams = currentMeta.streams || new Map();
|
|
2841
2734
|
streams.set(streamId, { buffer, flushTimer });
|
|
2842
2735
|
|
|
2843
|
-
|
|
2736
|
+
setShadowMetadata(stateKey, path, {
|
|
2844
2737
|
...currentMeta,
|
|
2845
2738
|
streams,
|
|
2846
2739
|
});
|
|
@@ -2879,58 +2772,45 @@ function createProxyHandler<T>(
|
|
|
2879
2772
|
arraySetter: any
|
|
2880
2773
|
) => ReactNode
|
|
2881
2774
|
) => {
|
|
2775
|
+
console.log('meta outside', JSON.stringify(meta));
|
|
2882
2776
|
const StateListWrapper = () => {
|
|
2883
2777
|
const componentIdsRef = useRef<Map<string, string>>(new Map());
|
|
2884
2778
|
|
|
2885
|
-
const cacheKey =
|
|
2886
|
-
meta?.transforms && meta.transforms.length > 0
|
|
2887
|
-
? `${componentId}-${hashTransforms(meta.transforms)}`
|
|
2888
|
-
: `${componentId}-base`;
|
|
2889
|
-
|
|
2890
2779
|
const [updateTrigger, forceUpdate] = useState({});
|
|
2891
2780
|
|
|
2892
|
-
|
|
2893
|
-
const cached = getGlobalStore
|
|
2894
|
-
.getState()
|
|
2895
|
-
.getShadowMetadata(stateKey, path)
|
|
2896
|
-
?.transformCaches?.get(cacheKey);
|
|
2897
|
-
|
|
2898
|
-
let freshValidIds: string[];
|
|
2899
|
-
|
|
2900
|
-
if (cached && cached.validIds) {
|
|
2901
|
-
freshValidIds = cached.validIds;
|
|
2902
|
-
} else {
|
|
2903
|
-
freshValidIds = applyTransforms(
|
|
2904
|
-
stateKey,
|
|
2905
|
-
path,
|
|
2906
|
-
meta?.transforms
|
|
2907
|
-
);
|
|
2781
|
+
console.log('updateTrigger updateTrigger updateTrigger');
|
|
2908
2782
|
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2783
|
+
const validIds = applyTransforms(
|
|
2784
|
+
stateKey,
|
|
2785
|
+
path,
|
|
2786
|
+
meta?.transforms
|
|
2787
|
+
);
|
|
2788
|
+
//the above get the new coorect valid ids i need ot udpate the meta object with this info
|
|
2789
|
+
const arrayPathKey = path.length > 0 ? path.join('.') : 'root';
|
|
2790
|
+
const updatedMeta = {
|
|
2791
|
+
...meta,
|
|
2792
|
+
arrayViews: {
|
|
2793
|
+
...(meta?.arrayViews || {}),
|
|
2794
|
+
[arrayPathKey]: validIds, // Update the arrayViews with the new valid IDs
|
|
2795
|
+
},
|
|
2796
|
+
};
|
|
2917
2797
|
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2798
|
+
// Now use the updated meta when getting array data
|
|
2799
|
+
const { value: arrayValues } = getArrayData(
|
|
2800
|
+
stateKey,
|
|
2801
|
+
path,
|
|
2802
|
+
updatedMeta
|
|
2803
|
+
);
|
|
2921
2804
|
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
arrayValues: freshValues || [],
|
|
2925
|
-
};
|
|
2926
|
-
}, [cacheKey, updateTrigger]);
|
|
2805
|
+
console.log('validIds', validIds);
|
|
2806
|
+
console.log('arrayValues', arrayValues);
|
|
2927
2807
|
|
|
2928
2808
|
useEffect(() => {
|
|
2929
2809
|
const unsubscribe = getGlobalStore
|
|
2930
2810
|
.getState()
|
|
2931
2811
|
.subscribeToPath(stateKeyPathKey, (e) => {
|
|
2932
2812
|
// A data change has occurred for the source array.
|
|
2933
|
-
|
|
2813
|
+
console.log('changed array statelist ', e);
|
|
2934
2814
|
if (e.type === 'GET_SELECTED') {
|
|
2935
2815
|
return;
|
|
2936
2816
|
}
|
|
@@ -2970,14 +2850,13 @@ function createProxyHandler<T>(
|
|
|
2970
2850
|
return null;
|
|
2971
2851
|
}
|
|
2972
2852
|
|
|
2853
|
+
// Continue using updatedMeta for the rest of your logic instead of meta
|
|
2973
2854
|
const arraySetter = rebuildStateShape({
|
|
2974
2855
|
path,
|
|
2975
2856
|
componentId: componentId!,
|
|
2976
|
-
meta:
|
|
2977
|
-
...meta,
|
|
2978
|
-
validIds: validIds,
|
|
2979
|
-
},
|
|
2857
|
+
meta: updatedMeta, // Use updated meta here
|
|
2980
2858
|
});
|
|
2859
|
+
console.log('arrayValues', arrayValues);
|
|
2981
2860
|
|
|
2982
2861
|
return (
|
|
2983
2862
|
<>
|
|
@@ -2994,7 +2873,7 @@ function createProxyHandler<T>(
|
|
|
2994
2873
|
componentIdsRef.current.set(itemKey, itemComponentId);
|
|
2995
2874
|
}
|
|
2996
2875
|
|
|
2997
|
-
const itemPath = itemKey
|
|
2876
|
+
const itemPath = [...path, itemKey];
|
|
2998
2877
|
|
|
2999
2878
|
return createElement(MemoizedCogsItemWrapper, {
|
|
3000
2879
|
key: itemKey,
|
|
@@ -3016,16 +2895,18 @@ function createProxyHandler<T>(
|
|
|
3016
2895
|
}
|
|
3017
2896
|
if (prop === 'stateFlattenOn') {
|
|
3018
2897
|
return (fieldName: string) => {
|
|
2898
|
+
// FIX: Get the definitive list of IDs for the current view from meta.arrayViews.
|
|
2899
|
+
const arrayPathKey = path.join('.');
|
|
2900
|
+
const viewIds = meta?.arrayViews?.[arrayPathKey];
|
|
2901
|
+
|
|
3019
2902
|
const currentState = getGlobalStore
|
|
3020
2903
|
.getState()
|
|
3021
|
-
.getShadowValue(
|
|
3022
|
-
|
|
3023
|
-
|
|
2904
|
+
.getShadowValue(stateKey, path, viewIds);
|
|
2905
|
+
|
|
2906
|
+
if (!Array.isArray(currentState)) return [];
|
|
3024
2907
|
|
|
3025
2908
|
stateVersion++;
|
|
3026
|
-
|
|
3027
|
-
(val: any) => val[fieldName] ?? []
|
|
3028
|
-
);
|
|
2909
|
+
|
|
3029
2910
|
return rebuildStateShape({
|
|
3030
2911
|
path: [...path, '[*]', fieldName],
|
|
3031
2912
|
componentId: componentId!,
|
|
@@ -3035,36 +2916,55 @@ function createProxyHandler<T>(
|
|
|
3035
2916
|
}
|
|
3036
2917
|
if (prop === 'index') {
|
|
3037
2918
|
return (index: number) => {
|
|
3038
|
-
const
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
2919
|
+
const arrayPathKey = path.join('.');
|
|
2920
|
+
const viewIds = meta?.arrayViews?.[arrayPathKey];
|
|
2921
|
+
|
|
2922
|
+
if (viewIds) {
|
|
2923
|
+
const itemId = viewIds[index];
|
|
2924
|
+
if (!itemId) return undefined;
|
|
2925
|
+
return rebuildStateShape({
|
|
2926
|
+
path: [...path, itemId],
|
|
2927
|
+
componentId: componentId!,
|
|
2928
|
+
meta,
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
// ✅ FIX: Get the metadata and use the `arrayKeys` property.
|
|
2933
|
+
const shadowMeta = getShadowMetadata(stateKey, path);
|
|
2934
|
+
if (!shadowMeta?.arrayKeys) return undefined;
|
|
2935
|
+
|
|
2936
|
+
const itemId = shadowMeta.arrayKeys[index];
|
|
3047
2937
|
if (!itemId) return undefined;
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
const state = rebuildStateShape({
|
|
3052
|
-
path: itemId.split('.').slice(1) as string[],
|
|
2938
|
+
|
|
2939
|
+
return rebuildStateShape({
|
|
2940
|
+
path: [...path, itemId],
|
|
3053
2941
|
componentId: componentId!,
|
|
3054
2942
|
meta,
|
|
3055
2943
|
});
|
|
3056
|
-
return state;
|
|
3057
2944
|
};
|
|
3058
2945
|
}
|
|
3059
2946
|
if (prop === 'last') {
|
|
3060
2947
|
return () => {
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
2948
|
+
// ✅ FIX: Use getArrayData to get the keys for the current view (filtered or not).
|
|
2949
|
+
const { keys: currentViewIds } = getArrayData(stateKey, path, meta);
|
|
2950
|
+
|
|
2951
|
+
// If the array is empty, there is no last item.
|
|
2952
|
+
if (!currentViewIds || currentViewIds.length === 0) {
|
|
2953
|
+
return undefined;
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
// Get the unique ID of the last item in the current view.
|
|
2957
|
+
const lastItemKey = currentViewIds[currentViewIds.length - 1];
|
|
2958
|
+
|
|
2959
|
+
// If for some reason the key is invalid, return undefined.
|
|
2960
|
+
if (!lastItemKey) {
|
|
2961
|
+
return undefined;
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
// ✅ FIX: The new path uses the item's unique key, not its numerical index.
|
|
2965
|
+
const newPath = [...path, lastItemKey];
|
|
2966
|
+
|
|
2967
|
+
// Return a new proxy scoped to that specific item.
|
|
3068
2968
|
return rebuildStateShape({
|
|
3069
2969
|
path: newPath,
|
|
3070
2970
|
componentId: componentId!,
|
|
@@ -3078,11 +2978,6 @@ function createProxyHandler<T>(
|
|
|
3078
2978
|
index?: number
|
|
3079
2979
|
) => {
|
|
3080
2980
|
effectiveSetState(payload as any, path, { updateType: 'insert' });
|
|
3081
|
-
return rebuildStateShape({
|
|
3082
|
-
path,
|
|
3083
|
-
componentId: componentId!,
|
|
3084
|
-
meta,
|
|
3085
|
-
});
|
|
3086
2981
|
};
|
|
3087
2982
|
}
|
|
3088
2983
|
if (prop === 'uniqueInsert') {
|
|
@@ -3091,9 +2986,13 @@ function createProxyHandler<T>(
|
|
|
3091
2986
|
fields?: (keyof InferArrayElement<T>)[],
|
|
3092
2987
|
onMatch?: (existingItem: any) => any
|
|
3093
2988
|
) => {
|
|
3094
|
-
const currentArray =
|
|
3095
|
-
|
|
3096
|
-
|
|
2989
|
+
const { value: currentArray } = getScopedData(
|
|
2990
|
+
stateKey,
|
|
2991
|
+
path,
|
|
2992
|
+
meta
|
|
2993
|
+
) as {
|
|
2994
|
+
value: any[];
|
|
2995
|
+
};
|
|
3097
2996
|
const newValue = isFunction<T>(payload)
|
|
3098
2997
|
? payload(currentArray as any)
|
|
3099
2998
|
: (payload as any);
|
|
@@ -3123,167 +3022,133 @@ function createProxyHandler<T>(
|
|
|
3123
3022
|
}
|
|
3124
3023
|
};
|
|
3125
3024
|
}
|
|
3126
|
-
|
|
3127
3025
|
if (prop === 'cut') {
|
|
3128
3026
|
return (index?: number, options?: { waitForSync?: boolean }) => {
|
|
3129
|
-
const
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
const validKeys =
|
|
3133
|
-
meta?.validIds ??
|
|
3134
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
3135
|
-
?.arrayKeys;
|
|
3136
|
-
|
|
3137
|
-
if (!validKeys || validKeys.length === 0) return;
|
|
3027
|
+
const shadowMeta = getShadowMetadata(stateKey, path);
|
|
3028
|
+
if (!shadowMeta?.arrayKeys || shadowMeta.arrayKeys.length === 0)
|
|
3029
|
+
return;
|
|
3138
3030
|
|
|
3139
3031
|
const indexToCut =
|
|
3140
|
-
index
|
|
3141
|
-
?
|
|
3032
|
+
index === -1
|
|
3033
|
+
? shadowMeta.arrayKeys.length - 1
|
|
3142
3034
|
: index !== undefined
|
|
3143
3035
|
? index
|
|
3144
|
-
:
|
|
3036
|
+
: shadowMeta.arrayKeys.length - 1;
|
|
3145
3037
|
|
|
3146
|
-
const
|
|
3147
|
-
if (!
|
|
3038
|
+
const idToCut = shadowMeta.arrayKeys[indexToCut];
|
|
3039
|
+
if (!idToCut) return;
|
|
3148
3040
|
|
|
3149
|
-
|
|
3150
|
-
effectiveSetState(currentState, pathForCut, {
|
|
3041
|
+
effectiveSetState(null, [...path, idToCut], {
|
|
3151
3042
|
updateType: 'cut',
|
|
3152
3043
|
});
|
|
3153
3044
|
};
|
|
3154
3045
|
}
|
|
3155
3046
|
if (prop === 'cutSelected') {
|
|
3156
3047
|
return () => {
|
|
3157
|
-
const
|
|
3158
|
-
const currentState = getGlobalStore
|
|
3159
|
-
.getState()
|
|
3160
|
-
.getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
|
|
3161
|
-
if (!validKeys || validKeys.length === 0) return;
|
|
3048
|
+
const arrayKey = [stateKey, ...path].join('.');
|
|
3162
3049
|
|
|
3163
|
-
const
|
|
3050
|
+
const { keys: currentViewIds } = getArrayData(stateKey, path, meta);
|
|
3051
|
+
if (!currentViewIds || currentViewIds.length === 0) {
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
const selectedItemKey = getGlobalStore
|
|
3164
3055
|
.getState()
|
|
3165
|
-
.selectedIndicesMap.get(
|
|
3056
|
+
.selectedIndicesMap.get(arrayKey);
|
|
3166
3057
|
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3058
|
+
if (!selectedItemKey) {
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
const selectedId = selectedItemKey.split('.').pop() as string;
|
|
3062
|
+
if (!(currentViewIds as any[]).includes(selectedId!)) {
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
const pathForCut = selectedItemKey.split('.').slice(1);
|
|
3066
|
+
getGlobalStore.getState().clearSelectedIndex({ arrayKey });
|
|
3170
3067
|
|
|
3171
|
-
const
|
|
3172
|
-
indexToCut == -1 ? validKeys.length - 1 : indexToCut
|
|
3173
|
-
]
|
|
3174
|
-
?.split('.')
|
|
3175
|
-
.slice(1);
|
|
3176
|
-
getGlobalStore
|
|
3177
|
-
.getState()
|
|
3178
|
-
.clearSelectedIndex({ arrayKey: stateKeyPathKey });
|
|
3179
|
-
const parentPath = pathForCut?.slice(0, -1)!;
|
|
3068
|
+
const parentPath = pathForCut.slice(0, -1);
|
|
3180
3069
|
notifySelectionComponents(stateKey, parentPath);
|
|
3181
|
-
|
|
3070
|
+
|
|
3071
|
+
effectiveSetState(null, pathForCut, {
|
|
3182
3072
|
updateType: 'cut',
|
|
3183
3073
|
});
|
|
3184
3074
|
};
|
|
3185
3075
|
}
|
|
3186
3076
|
if (prop === 'cutByValue') {
|
|
3187
3077
|
return (value: string | number | boolean) => {
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
if (!relevantKeys) return;
|
|
3195
|
-
|
|
3196
|
-
let keyToCut: string | null = null;
|
|
3078
|
+
const {
|
|
3079
|
+
isArray,
|
|
3080
|
+
value: array,
|
|
3081
|
+
keys,
|
|
3082
|
+
} = getArrayData(stateKey, path, meta);
|
|
3197
3083
|
|
|
3198
|
-
|
|
3199
|
-
for (const key of relevantKeys) {
|
|
3200
|
-
const itemValue = getGlobalStore.getState().getShadowValue(key);
|
|
3201
|
-
if (itemValue === value) {
|
|
3202
|
-
keyToCut = key;
|
|
3203
|
-
break; // We found the key, no need to search further.
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3084
|
+
if (!isArray) return;
|
|
3206
3085
|
|
|
3207
|
-
|
|
3208
|
-
if (
|
|
3209
|
-
|
|
3210
|
-
|
|
3086
|
+
const found = findArrayItem(array, keys, (item) => item === value);
|
|
3087
|
+
if (found) {
|
|
3088
|
+
effectiveSetState(null, [...path, found.key], {
|
|
3089
|
+
updateType: 'cut',
|
|
3090
|
+
});
|
|
3211
3091
|
}
|
|
3212
3092
|
};
|
|
3213
3093
|
}
|
|
3214
3094
|
|
|
3215
3095
|
if (prop === 'toggleByValue') {
|
|
3216
3096
|
return (value: string | number | boolean) => {
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3097
|
+
const {
|
|
3098
|
+
isArray,
|
|
3099
|
+
value: array,
|
|
3100
|
+
keys,
|
|
3101
|
+
} = getArrayData(stateKey, path, meta);
|
|
3222
3102
|
|
|
3223
|
-
if (!
|
|
3103
|
+
if (!isArray) return;
|
|
3224
3104
|
|
|
3225
|
-
|
|
3105
|
+
const found = findArrayItem(array, keys, (item) => item === value);
|
|
3226
3106
|
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
if (itemValue === value) {
|
|
3232
|
-
keyToCut = key;
|
|
3233
|
-
break; // Found it!
|
|
3234
|
-
}
|
|
3235
|
-
}
|
|
3236
|
-
console.log('itemValue keyToCut', keyToCut);
|
|
3237
|
-
// Step 3: Act based on whether the key was found.
|
|
3238
|
-
if (keyToCut) {
|
|
3239
|
-
// Item exists, so we CUT it using its *actual* key.
|
|
3240
|
-
const itemPath = keyToCut.split('.').slice(1);
|
|
3241
|
-
console.log('itemValue keyToCut', keyToCut);
|
|
3242
|
-
effectiveSetState(value as any, itemPath, {
|
|
3107
|
+
if (found) {
|
|
3108
|
+
const pathForItem = [...path, found.key];
|
|
3109
|
+
|
|
3110
|
+
effectiveSetState(null, pathForItem, {
|
|
3243
3111
|
updateType: 'cut',
|
|
3244
3112
|
});
|
|
3245
3113
|
} else {
|
|
3246
|
-
// Item does not exist, so we INSERT it.
|
|
3247
3114
|
effectiveSetState(value as any, path, { updateType: 'insert' });
|
|
3248
3115
|
}
|
|
3249
3116
|
};
|
|
3250
3117
|
}
|
|
3251
3118
|
if (prop === 'findWith') {
|
|
3252
|
-
return (searchKey:
|
|
3253
|
-
const
|
|
3254
|
-
.getState()
|
|
3255
|
-
.getShadowMetadata(stateKey, path)?.arrayKeys;
|
|
3119
|
+
return (searchKey: string, searchValue: any) => {
|
|
3120
|
+
const { isArray, value, keys } = getArrayData(stateKey, path, meta);
|
|
3256
3121
|
|
|
3257
|
-
if (!
|
|
3258
|
-
throw new Error('
|
|
3122
|
+
if (!isArray) {
|
|
3123
|
+
throw new Error('findWith can only be used on arrays');
|
|
3259
3124
|
}
|
|
3260
3125
|
|
|
3261
|
-
|
|
3262
|
-
|
|
3126
|
+
const found = findArrayItem(
|
|
3127
|
+
value,
|
|
3128
|
+
keys,
|
|
3129
|
+
(item) => item?.[searchKey] === searchValue
|
|
3130
|
+
);
|
|
3263
3131
|
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
.
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
break;
|
|
3272
|
-
}
|
|
3132
|
+
// FIX: If found, return a proxy to the item by appending its key to the current path.
|
|
3133
|
+
if (found) {
|
|
3134
|
+
return rebuildStateShape({
|
|
3135
|
+
path: [...path, found.key], // e.g., ['itemInstances', 'inst-1', 'properties', 'prop-b']
|
|
3136
|
+
componentId: componentId!,
|
|
3137
|
+
meta,
|
|
3138
|
+
});
|
|
3273
3139
|
}
|
|
3274
3140
|
|
|
3141
|
+
// If not found, return an 'empty' proxy that will resolve to undefined on .get()
|
|
3142
|
+
// This prevents "cannot read property 'get' of undefined" errors.
|
|
3275
3143
|
return rebuildStateShape({
|
|
3276
|
-
path:
|
|
3144
|
+
path: [...path, `not_found_${uuidv4()}`],
|
|
3277
3145
|
componentId: componentId!,
|
|
3278
3146
|
meta,
|
|
3279
3147
|
});
|
|
3280
3148
|
};
|
|
3281
3149
|
}
|
|
3282
|
-
|
|
3283
3150
|
if (prop === 'cutThis') {
|
|
3284
|
-
|
|
3285
|
-
.getState()
|
|
3286
|
-
.getShadowValue(path.join('.'));
|
|
3151
|
+
const { value: shadowValue } = getScopedData(stateKey, path, meta);
|
|
3287
3152
|
|
|
3288
3153
|
return () => {
|
|
3289
3154
|
effectiveSetState(shadowValue, path, { updateType: 'cut' });
|
|
@@ -3293,16 +3158,8 @@ function createProxyHandler<T>(
|
|
|
3293
3158
|
if (prop === 'get') {
|
|
3294
3159
|
return () => {
|
|
3295
3160
|
registerComponentDependency(stateKey, componentId, path);
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
.getShadowValue(stateKeyPathKey, meta?.validIds);
|
|
3299
|
-
};
|
|
3300
|
-
}
|
|
3301
|
-
if (prop === 'getState') {
|
|
3302
|
-
return () => {
|
|
3303
|
-
return getGlobalStore
|
|
3304
|
-
.getState()
|
|
3305
|
-
.getShadowValue(stateKeyPathKey, meta?.validIds);
|
|
3161
|
+
const { value } = getScopedData(stateKey, path, meta);
|
|
3162
|
+
return value;
|
|
3306
3163
|
};
|
|
3307
3164
|
}
|
|
3308
3165
|
|
|
@@ -3323,32 +3180,27 @@ function createProxyHandler<T>(
|
|
|
3323
3180
|
}
|
|
3324
3181
|
if (prop === 'lastSynced') {
|
|
3325
3182
|
const syncKey = `${stateKey}:${path.join('.')}`;
|
|
3326
|
-
return
|
|
3183
|
+
return getSyncInfo(syncKey);
|
|
3327
3184
|
}
|
|
3328
3185
|
if (prop == 'getLocalStorage') {
|
|
3329
3186
|
return (key: string) =>
|
|
3330
3187
|
loadFromLocalStorage(sessionId + '-' + stateKey + '-' + key);
|
|
3331
3188
|
}
|
|
3332
|
-
|
|
3333
3189
|
if (prop === 'isSelected') {
|
|
3334
|
-
const
|
|
3335
|
-
|
|
3336
|
-
if (
|
|
3337
|
-
Array.isArray(
|
|
3338
|
-
getGlobalStore
|
|
3339
|
-
.getState()
|
|
3340
|
-
.getShadowValue(parentPath.join('.'), meta?.validIds)
|
|
3341
|
-
)
|
|
3342
|
-
) {
|
|
3343
|
-
const itemId = path[path.length - 1];
|
|
3344
|
-
const fullParentKey = parentPath.join('.');
|
|
3190
|
+
const parentPathArray = path.slice(0, -1);
|
|
3191
|
+
const parentMeta = getShadowMetadata(stateKey, parentPathArray);
|
|
3345
3192
|
|
|
3193
|
+
// FIX: Check if the parent is an array by looking for arrayKeys in its metadata.
|
|
3194
|
+
if (parentMeta?.arrayKeys) {
|
|
3195
|
+
const fullParentKey = stateKey + '.' + parentPathArray.join('.');
|
|
3346
3196
|
const selectedItemKey = getGlobalStore
|
|
3347
3197
|
.getState()
|
|
3348
3198
|
.selectedIndicesMap.get(fullParentKey);
|
|
3349
3199
|
|
|
3350
3200
|
const fullItemKey = stateKey + '.' + path.join('.');
|
|
3351
3201
|
|
|
3202
|
+
// Logic remains the same.
|
|
3203
|
+
notifySelectionComponents(stateKey, parentPathArray, undefined);
|
|
3352
3204
|
return selectedItemKey === fullItemKey;
|
|
3353
3205
|
}
|
|
3354
3206
|
return undefined;
|
|
@@ -3402,11 +3254,6 @@ function createProxyHandler<T>(
|
|
|
3402
3254
|
if (path.length == 0) {
|
|
3403
3255
|
if (prop === 'addZodValidation') {
|
|
3404
3256
|
return (zodErrors: any[]) => {
|
|
3405
|
-
const init = getGlobalStore
|
|
3406
|
-
.getState()
|
|
3407
|
-
.getInitialOptions(stateKey)?.validation;
|
|
3408
|
-
|
|
3409
|
-
// For each error, set shadow metadata
|
|
3410
3257
|
zodErrors.forEach((error) => {
|
|
3411
3258
|
const currentMeta =
|
|
3412
3259
|
getGlobalStore
|
|
@@ -3418,42 +3265,38 @@ function createProxyHandler<T>(
|
|
|
3418
3265
|
.setShadowMetadata(stateKey, error.path, {
|
|
3419
3266
|
...currentMeta,
|
|
3420
3267
|
validation: {
|
|
3421
|
-
status: '
|
|
3422
|
-
|
|
3268
|
+
status: 'INVALID',
|
|
3269
|
+
errors: [
|
|
3270
|
+
{
|
|
3271
|
+
source: 'client',
|
|
3272
|
+
message: error.message,
|
|
3273
|
+
severity: 'error',
|
|
3274
|
+
code: error.code,
|
|
3275
|
+
},
|
|
3276
|
+
],
|
|
3277
|
+
lastValidated: Date.now(),
|
|
3423
3278
|
validatedValue: undefined,
|
|
3424
3279
|
},
|
|
3425
3280
|
});
|
|
3426
|
-
getGlobalStore.getState().notifyPathSubscribers(error.path, {
|
|
3427
|
-
type: 'VALIDATION_FAILED',
|
|
3428
|
-
message: error.message,
|
|
3429
|
-
validatedValue: undefined,
|
|
3430
|
-
});
|
|
3431
3281
|
});
|
|
3432
3282
|
};
|
|
3433
3283
|
}
|
|
3434
3284
|
if (prop === 'clearZodValidation') {
|
|
3435
3285
|
return (path?: string[]) => {
|
|
3436
|
-
// Clear specific paths
|
|
3437
3286
|
if (!path) {
|
|
3438
3287
|
throw new Error('clearZodValidation requires a path');
|
|
3439
|
-
return;
|
|
3440
3288
|
}
|
|
3441
|
-
const currentMeta =
|
|
3442
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
3443
|
-
{};
|
|
3444
|
-
|
|
3445
|
-
if (currentMeta.validation) {
|
|
3446
|
-
getGlobalStore.getState().setShadowMetadata(stateKey, path, {
|
|
3447
|
-
...currentMeta,
|
|
3448
|
-
validation: undefined,
|
|
3449
|
-
});
|
|
3450
3289
|
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3290
|
+
const currentMeta = getShadowMetadata(stateKey, path) || {};
|
|
3291
|
+
|
|
3292
|
+
setShadowMetadata(stateKey, path, {
|
|
3293
|
+
...currentMeta,
|
|
3294
|
+
validation: {
|
|
3295
|
+
status: 'NOT_VALIDATED',
|
|
3296
|
+
errors: [],
|
|
3297
|
+
lastValidated: Date.now(),
|
|
3298
|
+
},
|
|
3299
|
+
});
|
|
3457
3300
|
};
|
|
3458
3301
|
}
|
|
3459
3302
|
if (prop === 'applyJsonPatch') {
|
|
@@ -3484,6 +3327,7 @@ function createProxyHandler<T>(
|
|
|
3484
3327
|
value: any;
|
|
3485
3328
|
};
|
|
3486
3329
|
store.updateShadowAtPath(stateKey, relativePath, value);
|
|
3330
|
+
|
|
3487
3331
|
store.markAsDirty(stateKey, relativePath, { bubble: true });
|
|
3488
3332
|
|
|
3489
3333
|
// Bubble up - notify components at this path and all parent paths
|
|
@@ -3548,9 +3392,7 @@ function createProxyHandler<T>(
|
|
|
3548
3392
|
}
|
|
3549
3393
|
|
|
3550
3394
|
if (prop === 'getComponents')
|
|
3551
|
-
return () =>
|
|
3552
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, [])
|
|
3553
|
-
?.components;
|
|
3395
|
+
return () => getShadowMetadata(stateKey, [])?.components;
|
|
3554
3396
|
if (prop === 'getAllFormRefs')
|
|
3555
3397
|
return () =>
|
|
3556
3398
|
formRefStore.getState().getFormRefsByStateKey(stateKey);
|
|
@@ -3581,80 +3423,34 @@ function createProxyHandler<T>(
|
|
|
3581
3423
|
if (prop === '_stateKey') return stateKey;
|
|
3582
3424
|
if (prop === '_path') return path;
|
|
3583
3425
|
if (prop === 'update') {
|
|
3426
|
+
// This method is now greatly simplified.
|
|
3427
|
+
// All the complex batching logic has been removed because our new,
|
|
3428
|
+
// universal `createEffectiveSetState` function handles it automatically for all operations.
|
|
3584
3429
|
return (payload: UpdateArg<T>) => {
|
|
3585
|
-
//
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
const inReactEvent =
|
|
3589
|
-
stack.includes('onClick') ||
|
|
3590
|
-
stack.includes('dispatchEvent') ||
|
|
3591
|
-
stack.includes('batchedUpdates');
|
|
3592
|
-
|
|
3593
|
-
// Only batch if we're in a React event
|
|
3594
|
-
if (inReactEvent) {
|
|
3595
|
-
const batchKey = `${stateKey}.${path.join('.')}`;
|
|
3596
|
-
|
|
3597
|
-
// Schedule flush if not already scheduled
|
|
3598
|
-
if (!batchFlushScheduled) {
|
|
3599
|
-
updateBatchQueue.clear();
|
|
3600
|
-
batchFlushScheduled = true;
|
|
3601
|
-
|
|
3602
|
-
queueMicrotask(() => {
|
|
3603
|
-
// Process all batched updates
|
|
3604
|
-
for (const [key, updates] of updateBatchQueue) {
|
|
3605
|
-
const parts = key.split('.');
|
|
3606
|
-
const batchStateKey = parts[0];
|
|
3607
|
-
const batchPath = parts.slice(1);
|
|
3608
|
-
|
|
3609
|
-
// Compose all updates for this path
|
|
3610
|
-
const composedUpdate = updates.reduce(
|
|
3611
|
-
(composed, update) => {
|
|
3612
|
-
if (
|
|
3613
|
-
typeof update === 'function' &&
|
|
3614
|
-
typeof composed === 'function'
|
|
3615
|
-
) {
|
|
3616
|
-
// Compose functions
|
|
3617
|
-
return (state: any) => update(composed(state));
|
|
3618
|
-
}
|
|
3619
|
-
// If not functions, last one wins
|
|
3620
|
-
return update;
|
|
3621
|
-
}
|
|
3622
|
-
);
|
|
3623
|
-
|
|
3624
|
-
// Call effectiveSetState ONCE with composed update
|
|
3625
|
-
effectiveSetState(composedUpdate as any, batchPath, {
|
|
3626
|
-
updateType: 'update',
|
|
3627
|
-
});
|
|
3628
|
-
}
|
|
3629
|
-
|
|
3630
|
-
updateBatchQueue.clear();
|
|
3631
|
-
batchFlushScheduled = false;
|
|
3632
|
-
});
|
|
3633
|
-
}
|
|
3634
|
-
|
|
3635
|
-
// Add to batch
|
|
3636
|
-
const existing = updateBatchQueue.get(batchKey) || [];
|
|
3637
|
-
existing.push(payload);
|
|
3638
|
-
updateBatchQueue.set(batchKey, existing);
|
|
3639
|
-
} else {
|
|
3640
|
-
effectiveSetState(payload as any, path, { updateType: 'update' });
|
|
3641
|
-
}
|
|
3430
|
+
// Simply call effectiveSetState. It will automatically handle queuing
|
|
3431
|
+
// this operation in the batch for efficient processing.
|
|
3432
|
+
effectiveSetState(payload as any, path, { updateType: 'update' });
|
|
3642
3433
|
|
|
3434
|
+
// The .synced() method is a useful feature that allows developers
|
|
3435
|
+
// to manually mark a piece of state as "synced with the server"
|
|
3436
|
+
// after an update. This part of the functionality is preserved.
|
|
3643
3437
|
return {
|
|
3644
3438
|
synced: () => {
|
|
3645
3439
|
const shadowMeta = getGlobalStore
|
|
3646
3440
|
.getState()
|
|
3647
3441
|
.getShadowMetadata(stateKey, path);
|
|
3648
3442
|
|
|
3649
|
-
|
|
3443
|
+
// Update the metadata for this specific path
|
|
3444
|
+
setShadowMetadata(stateKey, path, {
|
|
3650
3445
|
...shadowMeta,
|
|
3651
3446
|
isDirty: false,
|
|
3652
3447
|
stateSource: 'server',
|
|
3653
3448
|
lastServerSync: Date.now(),
|
|
3654
3449
|
});
|
|
3655
3450
|
|
|
3451
|
+
// Notify any components that might be subscribed to the sync status
|
|
3656
3452
|
const fullPath = [stateKey, ...path].join('.');
|
|
3657
|
-
|
|
3453
|
+
notifyPathSubscribers(fullPath, {
|
|
3658
3454
|
type: 'SYNC_STATUS_CHANGE',
|
|
3659
3455
|
isDirty: false,
|
|
3660
3456
|
});
|
|
@@ -3662,11 +3458,12 @@ function createProxyHandler<T>(
|
|
|
3662
3458
|
};
|
|
3663
3459
|
};
|
|
3664
3460
|
}
|
|
3665
|
-
|
|
3666
3461
|
if (prop === 'toggle') {
|
|
3667
|
-
const currentValueAtPath =
|
|
3668
|
-
|
|
3669
|
-
|
|
3462
|
+
const { value: currentValueAtPath } = getScopedData(
|
|
3463
|
+
stateKey,
|
|
3464
|
+
path,
|
|
3465
|
+
meta
|
|
3466
|
+
);
|
|
3670
3467
|
|
|
3671
3468
|
if (typeof currentValueAtPath != 'boolean') {
|
|
3672
3469
|
throw new Error('toggle() can only be used on boolean values');
|
|
@@ -3703,18 +3500,14 @@ function createProxyHandler<T>(
|
|
|
3703
3500
|
},
|
|
3704
3501
|
};
|
|
3705
3502
|
|
|
3706
|
-
const proxyInstance = new Proxy(
|
|
3503
|
+
const proxyInstance = new Proxy({}, handler);
|
|
3707
3504
|
proxyCache.set(cacheKey, proxyInstance);
|
|
3708
3505
|
|
|
3709
3506
|
return proxyInstance;
|
|
3710
3507
|
}
|
|
3711
3508
|
|
|
3712
|
-
const
|
|
3509
|
+
const rootLevelMethods = {
|
|
3713
3510
|
revertToInitialState: (obj?: { validationKey?: string }) => {
|
|
3714
|
-
const init = getGlobalStore
|
|
3715
|
-
.getState()
|
|
3716
|
-
.getInitialOptions(stateKey)?.validation;
|
|
3717
|
-
|
|
3718
3511
|
const shadowMeta = getGlobalStore
|
|
3719
3512
|
.getState()
|
|
3720
3513
|
.getShadowMetadata(stateKey, []);
|
|
@@ -3730,10 +3523,10 @@ function createProxyHandler<T>(
|
|
|
3730
3523
|
const initialState =
|
|
3731
3524
|
getGlobalStore.getState().initialStateGlobal[stateKey];
|
|
3732
3525
|
|
|
3733
|
-
|
|
3526
|
+
clearSelectedIndexesForState(stateKey);
|
|
3734
3527
|
|
|
3735
3528
|
stateVersion++;
|
|
3736
|
-
|
|
3529
|
+
initializeShadowState(stateKey, initialState);
|
|
3737
3530
|
rebuildStateShape({
|
|
3738
3531
|
path: [],
|
|
3739
3532
|
componentId: componentId!,
|
|
@@ -3783,7 +3576,8 @@ function createProxyHandler<T>(
|
|
|
3783
3576
|
}
|
|
3784
3577
|
startTransition(() => {
|
|
3785
3578
|
updateInitialStateGlobal(stateKey, newState);
|
|
3786
|
-
|
|
3579
|
+
initializeShadowState(stateKey, newState);
|
|
3580
|
+
// initializeShadowStateNEW(stateKey, newState);
|
|
3787
3581
|
|
|
3788
3582
|
const stateEntry = getGlobalStore
|
|
3789
3583
|
.getState()
|
|
@@ -3801,6 +3595,7 @@ function createProxyHandler<T>(
|
|
|
3801
3595
|
};
|
|
3802
3596
|
},
|
|
3803
3597
|
};
|
|
3598
|
+
|
|
3804
3599
|
const returnShape = rebuildStateShape({
|
|
3805
3600
|
componentId,
|
|
3806
3601
|
path: [],
|
|
@@ -3819,153 +3614,6 @@ export function $cogsSignal(proxy: {
|
|
|
3819
3614
|
return createElement(SignalRenderer, { proxy });
|
|
3820
3615
|
}
|
|
3821
3616
|
|
|
3822
|
-
function SignalMapRenderer({
|
|
3823
|
-
proxy,
|
|
3824
|
-
rebuildStateShape,
|
|
3825
|
-
}: {
|
|
3826
|
-
proxy: {
|
|
3827
|
-
_stateKey: string;
|
|
3828
|
-
_path: string[];
|
|
3829
|
-
_meta?: MetaData;
|
|
3830
|
-
_mapFn: (
|
|
3831
|
-
setter: any,
|
|
3832
|
-
index: number,
|
|
3833
|
-
|
|
3834
|
-
arraySetter: any
|
|
3835
|
-
) => ReactNode;
|
|
3836
|
-
};
|
|
3837
|
-
rebuildStateShape: (stuff: {
|
|
3838
|
-
currentState: any;
|
|
3839
|
-
path: string[];
|
|
3840
|
-
componentId: string;
|
|
3841
|
-
meta?: MetaData;
|
|
3842
|
-
}) => any;
|
|
3843
|
-
}): JSX.Element | null {
|
|
3844
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
3845
|
-
const instanceIdRef = useRef<string>(`map-${crypto.randomUUID()}`);
|
|
3846
|
-
const isSetupRef = useRef(false);
|
|
3847
|
-
const rootsMapRef = useRef<Map<string, any>>(new Map());
|
|
3848
|
-
|
|
3849
|
-
// Setup effect - store the map function in shadow metadata
|
|
3850
|
-
useEffect(() => {
|
|
3851
|
-
const container = containerRef.current;
|
|
3852
|
-
if (!container || isSetupRef.current) return;
|
|
3853
|
-
|
|
3854
|
-
const timeoutId = setTimeout(() => {
|
|
3855
|
-
// Store map wrapper in metadata
|
|
3856
|
-
const currentMeta =
|
|
3857
|
-
getGlobalStore
|
|
3858
|
-
.getState()
|
|
3859
|
-
.getShadowMetadata(proxy._stateKey, proxy._path) || {};
|
|
3860
|
-
|
|
3861
|
-
const mapWrappers = currentMeta.mapWrappers || [];
|
|
3862
|
-
mapWrappers.push({
|
|
3863
|
-
instanceId: instanceIdRef.current,
|
|
3864
|
-
mapFn: proxy._mapFn,
|
|
3865
|
-
containerRef: container,
|
|
3866
|
-
rebuildStateShape: rebuildStateShape,
|
|
3867
|
-
path: proxy._path,
|
|
3868
|
-
componentId: instanceIdRef.current,
|
|
3869
|
-
meta: proxy._meta,
|
|
3870
|
-
});
|
|
3871
|
-
|
|
3872
|
-
getGlobalStore
|
|
3873
|
-
.getState()
|
|
3874
|
-
.setShadowMetadata(proxy._stateKey, proxy._path, {
|
|
3875
|
-
...currentMeta,
|
|
3876
|
-
mapWrappers,
|
|
3877
|
-
});
|
|
3878
|
-
|
|
3879
|
-
isSetupRef.current = true;
|
|
3880
|
-
|
|
3881
|
-
// Initial render
|
|
3882
|
-
renderInitialItems();
|
|
3883
|
-
}, 0);
|
|
3884
|
-
|
|
3885
|
-
// Cleanup
|
|
3886
|
-
return () => {
|
|
3887
|
-
clearTimeout(timeoutId);
|
|
3888
|
-
if (instanceIdRef.current) {
|
|
3889
|
-
const currentMeta =
|
|
3890
|
-
getGlobalStore
|
|
3891
|
-
.getState()
|
|
3892
|
-
.getShadowMetadata(proxy._stateKey, proxy._path) || {};
|
|
3893
|
-
if (currentMeta.mapWrappers) {
|
|
3894
|
-
currentMeta.mapWrappers = currentMeta.mapWrappers.filter(
|
|
3895
|
-
(w) => w.instanceId !== instanceIdRef.current
|
|
3896
|
-
);
|
|
3897
|
-
getGlobalStore
|
|
3898
|
-
.getState()
|
|
3899
|
-
.setShadowMetadata(proxy._stateKey, proxy._path, currentMeta);
|
|
3900
|
-
}
|
|
3901
|
-
}
|
|
3902
|
-
rootsMapRef.current.forEach((root) => root.unmount());
|
|
3903
|
-
};
|
|
3904
|
-
}, []);
|
|
3905
|
-
|
|
3906
|
-
const renderInitialItems = () => {
|
|
3907
|
-
const container = containerRef.current;
|
|
3908
|
-
if (!container) return;
|
|
3909
|
-
|
|
3910
|
-
const value = getGlobalStore
|
|
3911
|
-
.getState()
|
|
3912
|
-
.getShadowValue(
|
|
3913
|
-
[proxy._stateKey, ...proxy._path].join('.'),
|
|
3914
|
-
proxy._meta?.validIds
|
|
3915
|
-
) as any[];
|
|
3916
|
-
|
|
3917
|
-
if (!Array.isArray(value)) return;
|
|
3918
|
-
|
|
3919
|
-
// --- BUG FIX IS HERE ---
|
|
3920
|
-
// Prioritize the filtered IDs from the meta object, just like the regular `stateMap`.
|
|
3921
|
-
// This ensures the keys match the filtered data.
|
|
3922
|
-
const arrayKeys =
|
|
3923
|
-
proxy._meta?.validIds ??
|
|
3924
|
-
getGlobalStore.getState().getShadowMetadata(proxy._stateKey, proxy._path)
|
|
3925
|
-
?.arrayKeys ??
|
|
3926
|
-
[];
|
|
3927
|
-
// --- END OF FIX ---
|
|
3928
|
-
|
|
3929
|
-
const arraySetter = rebuildStateShape({
|
|
3930
|
-
currentState: value,
|
|
3931
|
-
path: proxy._path,
|
|
3932
|
-
componentId: instanceIdRef.current,
|
|
3933
|
-
meta: proxy._meta,
|
|
3934
|
-
});
|
|
3935
|
-
|
|
3936
|
-
value.forEach((item, index) => {
|
|
3937
|
-
const itemKey = arrayKeys[index]!; // Now this will be the correct key for the filtered item
|
|
3938
|
-
if (!itemKey) return; // Safeguard if there's a mismatch
|
|
3939
|
-
|
|
3940
|
-
const itemComponentId = uuidv4();
|
|
3941
|
-
const itemElement = document.createElement('div');
|
|
3942
|
-
|
|
3943
|
-
itemElement.setAttribute('data-item-path', itemKey);
|
|
3944
|
-
container.appendChild(itemElement);
|
|
3945
|
-
|
|
3946
|
-
const root = createRoot(itemElement);
|
|
3947
|
-
rootsMapRef.current.set(itemKey, root);
|
|
3948
|
-
|
|
3949
|
-
const itemPath = itemKey.split('.').slice(1) as string[];
|
|
3950
|
-
|
|
3951
|
-
// Render CogsItemWrapper instead of direct render
|
|
3952
|
-
root.render(
|
|
3953
|
-
createElement(MemoizedCogsItemWrapper, {
|
|
3954
|
-
stateKey: proxy._stateKey,
|
|
3955
|
-
itemComponentId: itemComponentId,
|
|
3956
|
-
itemPath: itemPath,
|
|
3957
|
-
localIndex: index,
|
|
3958
|
-
arraySetter: arraySetter,
|
|
3959
|
-
rebuildStateShape: rebuildStateShape,
|
|
3960
|
-
renderFn: proxy._mapFn,
|
|
3961
|
-
})
|
|
3962
|
-
);
|
|
3963
|
-
});
|
|
3964
|
-
};
|
|
3965
|
-
|
|
3966
|
-
return <div ref={containerRef} data-map-container={instanceIdRef.current} />;
|
|
3967
|
-
}
|
|
3968
|
-
|
|
3969
3617
|
function SignalRenderer({
|
|
3970
3618
|
proxy,
|
|
3971
3619
|
}: {
|
|
@@ -3980,12 +3628,10 @@ function SignalRenderer({
|
|
|
3980
3628
|
const instanceIdRef = useRef<string | null>(null);
|
|
3981
3629
|
const isSetupRef = useRef(false);
|
|
3982
3630
|
const signalId = `${proxy._stateKey}-${proxy._path.join('.')}`;
|
|
3983
|
-
const
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
proxy._meta?.validIds
|
|
3988
|
-
);
|
|
3631
|
+
const arrayPathKey = proxy._path.join('.');
|
|
3632
|
+
const viewIds = proxy._meta?.arrayViews?.[arrayPathKey];
|
|
3633
|
+
|
|
3634
|
+
const value = getShadowValue(proxy._stateKey, proxy._path, viewIds);
|
|
3989
3635
|
|
|
3990
3636
|
// Setup effect - runs only once
|
|
3991
3637
|
useEffect(() => {
|
|
@@ -4180,7 +3826,7 @@ function ListItemWrapper({
|
|
|
4180
3826
|
);
|
|
4181
3827
|
|
|
4182
3828
|
useEffect(() => {
|
|
4183
|
-
|
|
3829
|
+
subscribeToPath(fullKey, (e) => {
|
|
4184
3830
|
forceUpdate({});
|
|
4185
3831
|
});
|
|
4186
3832
|
}, []);
|
|
@@ -4194,7 +3840,7 @@ function ListItemWrapper({
|
|
|
4194
3840
|
hasReportedInitialHeight.current = true;
|
|
4195
3841
|
const newHeight = element.offsetHeight;
|
|
4196
3842
|
|
|
4197
|
-
|
|
3843
|
+
setShadowMetadata(stateKey, itemPath, {
|
|
4198
3844
|
virtualizer: {
|
|
4199
3845
|
itemHeight: newHeight,
|
|
4200
3846
|
domRef: element,
|
|
@@ -4203,7 +3849,7 @@ function ListItemWrapper({
|
|
|
4203
3849
|
|
|
4204
3850
|
const arrayPath = itemPath.slice(0, -1);
|
|
4205
3851
|
const arrayPathKey = [stateKey, ...arrayPath].join('.');
|
|
4206
|
-
|
|
3852
|
+
notifyPathSubscribers(arrayPathKey, {
|
|
4207
3853
|
type: 'ITEMHEIGHT',
|
|
4208
3854
|
itemKey: itemPath.join('.'),
|
|
4209
3855
|
|
|
@@ -4212,8 +3858,7 @@ function ListItemWrapper({
|
|
|
4212
3858
|
}
|
|
4213
3859
|
}, [inView, imagesLoaded, stateKey, itemPath]);
|
|
4214
3860
|
|
|
4215
|
-
const
|
|
4216
|
-
const itemValue = getGlobalStore.getState().getShadowValue(fullItemPath);
|
|
3861
|
+
const itemValue = getShadowValue(stateKey, itemPath);
|
|
4217
3862
|
|
|
4218
3863
|
if (itemValue === undefined) {
|
|
4219
3864
|
return null;
|
|
@@ -4240,7 +3885,6 @@ function FormElementWrapper({
|
|
|
4240
3885
|
stateKey: string;
|
|
4241
3886
|
path: string[];
|
|
4242
3887
|
rebuildStateShape: (options: {
|
|
4243
|
-
currentState: any;
|
|
4244
3888
|
path: string[];
|
|
4245
3889
|
componentId: string;
|
|
4246
3890
|
meta?: any;
|
|
@@ -4254,9 +3898,7 @@ function FormElementWrapper({
|
|
|
4254
3898
|
|
|
4255
3899
|
const stateKeyPathKey = [stateKey, ...path].join('.');
|
|
4256
3900
|
useRegisterComponent(stateKey, componentId, forceUpdate);
|
|
4257
|
-
const globalStateValue =
|
|
4258
|
-
.getState()
|
|
4259
|
-
.getShadowValue(stateKeyPathKey);
|
|
3901
|
+
const globalStateValue = getShadowValue(stateKey, path);
|
|
4260
3902
|
const [localValue, setLocalValue] = useState<any>(globalStateValue);
|
|
4261
3903
|
const isCurrentlyDebouncing = useRef(false);
|
|
4262
3904
|
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
@@ -4304,21 +3946,21 @@ function FormElementWrapper({
|
|
|
4304
3946
|
|
|
4305
3947
|
debounceTimeoutRef.current = setTimeout(() => {
|
|
4306
3948
|
isCurrentlyDebouncing.current = false;
|
|
4307
|
-
|
|
4308
|
-
// Update state
|
|
4309
3949
|
setState(newValue, path, { updateType: 'update' });
|
|
4310
3950
|
|
|
4311
|
-
//
|
|
4312
|
-
const
|
|
4313
|
-
|
|
3951
|
+
// NEW: Check if validation is enabled via features
|
|
3952
|
+
const rootMeta = getGlobalStore
|
|
3953
|
+
.getState()
|
|
3954
|
+
.getShadowMetadata(stateKey, []);
|
|
3955
|
+
if (!rootMeta?.features?.validationEnabled) return;
|
|
3956
|
+
|
|
4314
3957
|
const validationOptions = getInitialOptions(stateKey)?.validation;
|
|
4315
3958
|
const zodSchema =
|
|
4316
3959
|
validationOptions?.zodSchemaV4 || validationOptions?.zodSchemaV3;
|
|
4317
3960
|
|
|
4318
3961
|
if (zodSchema) {
|
|
4319
|
-
const fullState =
|
|
3962
|
+
const fullState = getShadowValue(stateKey, []);
|
|
4320
3963
|
const result = zodSchema.safeParse(fullState);
|
|
4321
|
-
|
|
4322
3964
|
const currentMeta = getShadowMetadata(stateKey, path) || {};
|
|
4323
3965
|
|
|
4324
3966
|
if (!result.success) {
|
|
@@ -4326,6 +3968,7 @@ function FormElementWrapper({
|
|
|
4326
3968
|
'issues' in result.error
|
|
4327
3969
|
? result.error.issues
|
|
4328
3970
|
: (result.error as any).errors;
|
|
3971
|
+
|
|
4329
3972
|
const pathErrors = errors.filter(
|
|
4330
3973
|
(error: any) =>
|
|
4331
3974
|
JSON.stringify(error.path) === JSON.stringify(path)
|
|
@@ -4335,30 +3978,37 @@ function FormElementWrapper({
|
|
|
4335
3978
|
setShadowMetadata(stateKey, path, {
|
|
4336
3979
|
...currentMeta,
|
|
4337
3980
|
validation: {
|
|
4338
|
-
status: '
|
|
4339
|
-
|
|
3981
|
+
status: 'INVALID',
|
|
3982
|
+
errors: [
|
|
3983
|
+
{
|
|
3984
|
+
source: 'client',
|
|
3985
|
+
message: pathErrors[0]?.message,
|
|
3986
|
+
severity: 'warning', // Gentle error during typing
|
|
3987
|
+
},
|
|
3988
|
+
],
|
|
3989
|
+
lastValidated: Date.now(),
|
|
4340
3990
|
validatedValue: newValue,
|
|
4341
3991
|
},
|
|
4342
3992
|
});
|
|
4343
3993
|
} else {
|
|
4344
|
-
// This field has no errors - clear validation
|
|
4345
3994
|
setShadowMetadata(stateKey, path, {
|
|
4346
3995
|
...currentMeta,
|
|
4347
3996
|
validation: {
|
|
4348
|
-
status: '
|
|
3997
|
+
status: 'VALID',
|
|
3998
|
+
errors: [],
|
|
3999
|
+
lastValidated: Date.now(),
|
|
4349
4000
|
validatedValue: newValue,
|
|
4350
|
-
message: undefined,
|
|
4351
4001
|
},
|
|
4352
4002
|
});
|
|
4353
4003
|
}
|
|
4354
4004
|
} else {
|
|
4355
|
-
// Validation passed - clear any existing errors
|
|
4356
4005
|
setShadowMetadata(stateKey, path, {
|
|
4357
4006
|
...currentMeta,
|
|
4358
4007
|
validation: {
|
|
4359
|
-
status: '
|
|
4008
|
+
status: 'VALID',
|
|
4009
|
+
errors: [],
|
|
4010
|
+
lastValidated: Date.now(),
|
|
4360
4011
|
validatedValue: newValue,
|
|
4361
|
-
message: undefined,
|
|
4362
4012
|
},
|
|
4363
4013
|
});
|
|
4364
4014
|
}
|
|
@@ -4381,7 +4031,8 @@ function FormElementWrapper({
|
|
|
4381
4031
|
isCurrentlyDebouncing.current = false;
|
|
4382
4032
|
setState(localValue, path, { updateType: 'update' });
|
|
4383
4033
|
}
|
|
4384
|
-
|
|
4034
|
+
const rootMeta = getShadowMetadata(stateKey, []);
|
|
4035
|
+
if (!rootMeta?.features?.validationEnabled) return;
|
|
4385
4036
|
const { getInitialOptions } = getGlobalStore.getState();
|
|
4386
4037
|
const validationOptions = getInitialOptions(stateKey)?.validation;
|
|
4387
4038
|
const zodSchema =
|
|
@@ -4392,19 +4043,20 @@ function FormElementWrapper({
|
|
|
4392
4043
|
// Get the full path including stateKey
|
|
4393
4044
|
|
|
4394
4045
|
// Update validation state to "validating"
|
|
4395
|
-
const currentMeta =
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
getGlobalStore.getState().setShadowMetadata(stateKey, path, {
|
|
4046
|
+
const currentMeta = getShadowMetadata(stateKey, path);
|
|
4047
|
+
|
|
4048
|
+
setShadowMetadata(stateKey, path, {
|
|
4399
4049
|
...currentMeta,
|
|
4400
4050
|
validation: {
|
|
4401
|
-
status: '
|
|
4051
|
+
status: 'VALIDATING',
|
|
4052
|
+
errors: [],
|
|
4053
|
+
lastValidated: Date.now(),
|
|
4402
4054
|
validatedValue: localValue,
|
|
4403
4055
|
},
|
|
4404
4056
|
});
|
|
4405
4057
|
|
|
4406
4058
|
// Validate full state
|
|
4407
|
-
const fullState =
|
|
4059
|
+
const fullState = getShadowValue(stateKey, []);
|
|
4408
4060
|
const result = zodSchema.safeParse(fullState);
|
|
4409
4061
|
console.log('result ', result);
|
|
4410
4062
|
if (!result.success) {
|
|
@@ -4468,20 +4120,27 @@ function FormElementWrapper({
|
|
|
4468
4120
|
|
|
4469
4121
|
console.log('Filtered path errors:', pathErrors);
|
|
4470
4122
|
// Update shadow metadata with validation result
|
|
4471
|
-
|
|
4123
|
+
setShadowMetadata(stateKey, path, {
|
|
4472
4124
|
...currentMeta,
|
|
4473
4125
|
validation: {
|
|
4474
|
-
status: '
|
|
4475
|
-
|
|
4126
|
+
status: 'INVALID',
|
|
4127
|
+
errors: pathErrors.map((err: any) => ({
|
|
4128
|
+
source: 'client' as const,
|
|
4129
|
+
message: err.message,
|
|
4130
|
+
severity: 'error' as const, // Hard error on blur
|
|
4131
|
+
})),
|
|
4132
|
+
lastValidated: Date.now(),
|
|
4476
4133
|
validatedValue: localValue,
|
|
4477
4134
|
},
|
|
4478
4135
|
});
|
|
4479
4136
|
} else {
|
|
4480
4137
|
// Validation passed
|
|
4481
|
-
|
|
4138
|
+
setShadowMetadata(stateKey, path, {
|
|
4482
4139
|
...currentMeta,
|
|
4483
4140
|
validation: {
|
|
4484
|
-
status: '
|
|
4141
|
+
status: 'VALID',
|
|
4142
|
+
errors: [],
|
|
4143
|
+
lastValidated: Date.now(),
|
|
4485
4144
|
validatedValue: localValue,
|
|
4486
4145
|
},
|
|
4487
4146
|
});
|
|
@@ -4490,7 +4149,6 @@ function FormElementWrapper({
|
|
|
4490
4149
|
}, [stateKey, path, localValue, setState]);
|
|
4491
4150
|
|
|
4492
4151
|
const baseState = rebuildStateShape({
|
|
4493
|
-
currentState: globalStateValue,
|
|
4494
4152
|
path: path,
|
|
4495
4153
|
componentId: componentId,
|
|
4496
4154
|
});
|
|
@@ -4529,9 +4187,6 @@ function useRegisterComponent(
|
|
|
4529
4187
|
const fullComponentId = `${stateKey}////${componentId}`;
|
|
4530
4188
|
|
|
4531
4189
|
useLayoutEffect(() => {
|
|
4532
|
-
const { registerComponent, unregisterComponent } =
|
|
4533
|
-
getGlobalStore.getState();
|
|
4534
|
-
|
|
4535
4190
|
// Call the safe, centralized function to register
|
|
4536
4191
|
registerComponent(stateKey, fullComponentId, {
|
|
4537
4192
|
forceUpdate: () => forceUpdate({}),
|