cogsbox-state 0.5.474 → 0.5.475-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/dist/CogsState.d.ts +26 -17
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +383 -380
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.jsx +231 -225
- package/dist/Components.jsx.map +1 -1
- package/dist/PluginRunner.d.ts.map +1 -1
- package/dist/PluginRunner.jsx +82 -67
- package/dist/PluginRunner.jsx.map +1 -1
- package/dist/index.js +17 -16
- package/dist/plugins.d.ts +98 -980
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +48 -36
- package/dist/plugins.js.map +1 -1
- package/dist/store.js +168 -169
- package/dist/store.js.map +1 -1
- package/package.json +4 -3
- package/src/CogsState.tsx +234 -551
- package/src/Components.tsx +43 -34
- package/src/PluginRunner.tsx +24 -6
- package/src/plugins.ts +93 -127
- package/src/store.ts +1 -1
package/src/CogsState.tsx
CHANGED
|
@@ -44,32 +44,12 @@ import { useCogsConfig } from './CogsStateClient.js';
|
|
|
44
44
|
import { Operation } from 'fast-json-patch';
|
|
45
45
|
|
|
46
46
|
import * as z3 from 'zod/v3';
|
|
47
|
-
import * as z4 from 'zod/v4';
|
|
48
47
|
|
|
49
48
|
import { runValidation } from './validation';
|
|
49
|
+
import { ZodType } from 'zod/v4';
|
|
50
50
|
|
|
51
51
|
export type Prettify<T> = T extends any ? { [K in keyof T]: T[K] } : never;
|
|
52
52
|
|
|
53
|
-
export type VirtualViewOptions = {
|
|
54
|
-
itemHeight?: number;
|
|
55
|
-
overscan?: number;
|
|
56
|
-
stickToBottom?: boolean;
|
|
57
|
-
dependencies?: any[];
|
|
58
|
-
scrollStickTolerance?: number;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// The result now returns a real StateObject
|
|
62
|
-
export type VirtualStateObjectResult<T extends any[]> = {
|
|
63
|
-
virtualState: StateObject<T>;
|
|
64
|
-
virtualizerProps: {
|
|
65
|
-
outer: { ref: RefObject<HTMLDivElement>; style: CSSProperties };
|
|
66
|
-
inner: { style: CSSProperties };
|
|
67
|
-
list: { style: CSSProperties };
|
|
68
|
-
};
|
|
69
|
-
scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
70
|
-
scrollToIndex: (index: number, behavior?: ScrollBehavior) => void;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
53
|
export type SyncInfo = {
|
|
74
54
|
timeStamp: number;
|
|
75
55
|
userId: number;
|
|
@@ -123,7 +103,10 @@ export type StreamHandle<T> = {
|
|
|
123
103
|
resume: () => void;
|
|
124
104
|
};
|
|
125
105
|
|
|
126
|
-
export type ArrayEndType<
|
|
106
|
+
export type ArrayEndType<
|
|
107
|
+
TShape extends unknown,
|
|
108
|
+
TPlugins extends readonly CogsPlugin<any, any, any, any, any>[],
|
|
109
|
+
> = {
|
|
127
110
|
$stream: <T = Prettify<InferArrayElement<TShape>>, R = T>(
|
|
128
111
|
options?: StreamOptions<T, R>
|
|
129
112
|
) => StreamHandle<T>;
|
|
@@ -141,24 +124,21 @@ export type ArrayEndType<TShape extends unknown> = {
|
|
|
141
124
|
$cutSelected: () => void;
|
|
142
125
|
$cutByValue: (value: string | number | boolean) => void;
|
|
143
126
|
$toggleByValue: (value: string | number | boolean) => void;
|
|
144
|
-
$
|
|
127
|
+
$sort: (
|
|
145
128
|
compareFn: (
|
|
146
129
|
a: Prettify<InferArrayElement<TShape>>,
|
|
147
130
|
b: Prettify<InferArrayElement<TShape>>
|
|
148
131
|
) => number
|
|
149
|
-
) => ArrayEndType<TShape>;
|
|
150
|
-
$useVirtualView: (
|
|
151
|
-
options: VirtualViewOptions
|
|
152
|
-
) => VirtualStateObjectResult<Prettify<InferArrayElement<TShape>>[]>;
|
|
132
|
+
) => ArrayEndType<TShape, TPlugins>;
|
|
153
133
|
|
|
154
|
-
$
|
|
134
|
+
$list: (
|
|
155
135
|
callbackfn: (
|
|
156
136
|
setter: StateObject<Prettify<InferArrayElement<TShape>>>,
|
|
157
137
|
index: number,
|
|
158
138
|
arraySetter: StateObject<TShape>
|
|
159
139
|
) => void
|
|
160
140
|
) => any;
|
|
161
|
-
$
|
|
141
|
+
$map: <U>(
|
|
162
142
|
callbackfn: (
|
|
163
143
|
setter: StateObject<Prettify<InferArrayElement<TShape>>>,
|
|
164
144
|
index: number,
|
|
@@ -174,18 +154,18 @@ export type ArrayEndType<TShape extends unknown> = {
|
|
|
174
154
|
fields?: (keyof Prettify<InferArrayElement<TShape>>)[],
|
|
175
155
|
onMatch?: (existingItem: any) => any
|
|
176
156
|
) => void;
|
|
177
|
-
$
|
|
157
|
+
$find: (
|
|
178
158
|
callbackfn: (
|
|
179
159
|
value: Prettify<InferArrayElement<TShape>>,
|
|
180
160
|
index: number
|
|
181
161
|
) => boolean
|
|
182
162
|
) => StateObject<Prettify<InferArrayElement<TShape>>> | undefined;
|
|
183
|
-
$
|
|
163
|
+
$filter: (
|
|
184
164
|
callbackfn: (
|
|
185
165
|
value: Prettify<InferArrayElement<TShape>>,
|
|
186
166
|
index: number
|
|
187
167
|
) => void
|
|
188
|
-
) => ArrayEndType<TShape>;
|
|
168
|
+
) => ArrayEndType<TShape, TPlugins>;
|
|
189
169
|
$getSelected: () =>
|
|
190
170
|
| StateObject<Prettify<InferArrayElement<TShape>>>
|
|
191
171
|
| undefined;
|
|
@@ -221,7 +201,17 @@ export type InsertType<T> = (payload: InsertParams<T>, index?: number) => void;
|
|
|
221
201
|
export type InsertTypeObj<T> = (payload: InsertParams<T>) => void;
|
|
222
202
|
|
|
223
203
|
type EffectFunction<T, R> = (state: T, deps: any[]) => R;
|
|
224
|
-
export type
|
|
204
|
+
export type PerPathFormOptsType<
|
|
205
|
+
TState,
|
|
206
|
+
TPlugins extends readonly CogsPlugin<any, any, any, any, any>[] = [],
|
|
207
|
+
> = Omit<FormOptsType, 'formElements'> & {
|
|
208
|
+
formElements?: FormsElementsType<TState, TPlugins>;
|
|
209
|
+
};
|
|
210
|
+
export type EndType<
|
|
211
|
+
T,
|
|
212
|
+
TPlugins extends readonly CogsPlugin<any, any, any, any, any>[] = [],
|
|
213
|
+
IsArrayElement = false,
|
|
214
|
+
> = {
|
|
225
215
|
$getPluginMetaData: (pluginName: string) => Record<string, any>;
|
|
226
216
|
$addPluginMetaData: (key: string, data: Record<string, any>) => void;
|
|
227
217
|
$removePluginMetaData: (key: string) => void;
|
|
@@ -241,18 +231,22 @@ export type EndType<T, IsArrayElement = false> = {
|
|
|
241
231
|
$_path: string[];
|
|
242
232
|
$_stateKey: string;
|
|
243
233
|
$isolate: (
|
|
244
|
-
renderFn: (state: StateObject<T>) => React.ReactNode
|
|
234
|
+
renderFn: (state: StateObject<T, TPlugins>) => React.ReactNode
|
|
235
|
+
) => JSX.Element;
|
|
236
|
+
$formElement: (
|
|
237
|
+
control: FormControl<T>,
|
|
238
|
+
opts?: PerPathFormOptsType<T, TPlugins>
|
|
245
239
|
) => JSX.Element;
|
|
246
|
-
$formElement: (control: FormControl<T>, opts?: FormOptsType) => JSX.Element;
|
|
247
240
|
$get: () => T;
|
|
248
241
|
$$get: () => T;
|
|
249
242
|
$$derive: <R>(fn: EffectFunction<T, R>) => R;
|
|
250
243
|
$_status: 'fresh' | 'dirty' | 'synced' | 'restored' | 'unknown';
|
|
251
244
|
$getStatus: () => 'fresh' | 'dirty' | 'synced' | 'restored' | 'unknown';
|
|
252
245
|
$showValidationErrors: () => string[];
|
|
246
|
+
|
|
253
247
|
$setValidation: (ctx: string) => void;
|
|
254
248
|
$removeValidation: (ctx: string) => void;
|
|
255
|
-
|
|
249
|
+
|
|
256
250
|
$isSelected: boolean;
|
|
257
251
|
$setSelected: (value: boolean) => void;
|
|
258
252
|
$toggleSelected: () => void;
|
|
@@ -276,16 +270,24 @@ export type EndType<T, IsArrayElement = false> = {
|
|
|
276
270
|
$lastSynced?: SyncInfo;
|
|
277
271
|
} & (IsArrayElement extends true ? { $cutThis: () => void } : {});
|
|
278
272
|
|
|
279
|
-
export type StateObject<
|
|
280
|
-
|
|
273
|
+
export type StateObject<
|
|
274
|
+
T,
|
|
275
|
+
TPlugins extends readonly CogsPlugin<any, any, any, any, any>[] = [],
|
|
276
|
+
> = {
|
|
277
|
+
// A. Callable Getter: state.count()
|
|
278
|
+
(): T;
|
|
279
|
+
// B. Callable Setter: state.count(5)
|
|
280
|
+
(newValue: T | ((prev: T) => T)): void;
|
|
281
|
+
} & (T extends any[]
|
|
282
|
+
? ArrayEndType<T, TPlugins>
|
|
281
283
|
: T extends Record<string, unknown> | object
|
|
282
|
-
? { [K in keyof T]-?: StateObject<T[K]> }
|
|
284
|
+
? { [K in keyof T]-?: StateObject<T[K], TPlugins> }
|
|
283
285
|
: T extends string | number | boolean | null
|
|
284
|
-
? EndType<T, true>
|
|
286
|
+
? EndType<T, TPlugins, true>
|
|
285
287
|
: never) &
|
|
286
|
-
EndType<T, true> & {
|
|
288
|
+
EndType<T, TPlugins, true> & {
|
|
287
289
|
$toggle: T extends boolean ? () => void : never;
|
|
288
|
-
|
|
290
|
+
$validate: () => { success: boolean; data?: T; error?: any };
|
|
289
291
|
$_componentId: string | null;
|
|
290
292
|
$getComponents: () => ComponentsType;
|
|
291
293
|
|
|
@@ -365,7 +367,7 @@ export type ReactivityType =
|
|
|
365
367
|
type ValidationOptionsType = {
|
|
366
368
|
key?: string;
|
|
367
369
|
zodSchemaV3?: z3.ZodType<any, any, any>;
|
|
368
|
-
zodSchemaV4?:
|
|
370
|
+
zodSchemaV4?: ZodType;
|
|
369
371
|
onBlur?: 'error' | 'warning';
|
|
370
372
|
onChange?: 'error' | 'warning';
|
|
371
373
|
blockSync?: boolean;
|
|
@@ -439,7 +441,12 @@ export type OptionsType<
|
|
|
439
441
|
|
|
440
442
|
dependencies?: any[];
|
|
441
443
|
};
|
|
442
|
-
|
|
444
|
+
type ScopedPluginApi<THookReturn, TFieldMetaData> = {
|
|
445
|
+
hookData: THookReturn;
|
|
446
|
+
getFieldMetaData: () => TFieldMetaData | undefined;
|
|
447
|
+
setFieldMetaData: (data: Partial<TFieldMetaData>) => void;
|
|
448
|
+
// Add any other scoped functions or data a plugin might need here
|
|
449
|
+
};
|
|
443
450
|
export type FormsElementsType<
|
|
444
451
|
TState,
|
|
445
452
|
TPlugins extends readonly CogsPlugin<any, any, any, any, any>[] = [],
|
|
@@ -455,6 +462,19 @@ export type FormsElementsType<
|
|
|
455
462
|
path: string[];
|
|
456
463
|
message?: string;
|
|
457
464
|
getData?: () => TState;
|
|
465
|
+
plugins: {
|
|
466
|
+
// It maps over the plugins...
|
|
467
|
+
[P in TPlugins[number] as P['name']]: P extends CogsPlugin<
|
|
468
|
+
any,
|
|
469
|
+
any,
|
|
470
|
+
infer THookReturn,
|
|
471
|
+
any,
|
|
472
|
+
infer TFieldMetaData
|
|
473
|
+
>
|
|
474
|
+
? // ...and provides the scoped API for each one.
|
|
475
|
+
ScopedPluginApi<THookReturn, TFieldMetaData>
|
|
476
|
+
: never;
|
|
477
|
+
};
|
|
458
478
|
}) => React.ReactNode;
|
|
459
479
|
syncRender?: (options: {
|
|
460
480
|
children: React.ReactNode;
|
|
@@ -462,16 +482,6 @@ export type FormsElementsType<
|
|
|
462
482
|
data?: TState;
|
|
463
483
|
key?: string;
|
|
464
484
|
}) => React.ReactNode;
|
|
465
|
-
} & {
|
|
466
|
-
// For each plugin `P` in the TPlugins array...
|
|
467
|
-
[P in TPlugins[number] as P['name']]?: P['formWrapper'] extends (
|
|
468
|
-
// ...check if its `formWrapper` property is a function...
|
|
469
|
-
arg: any
|
|
470
|
-
) => any
|
|
471
|
-
? // ...if it is, infer the type of its FIRST PARAMETER. This is the key.
|
|
472
|
-
Parameters<P['formWrapper']>[0]
|
|
473
|
-
: // Otherwise, the type is invalid.
|
|
474
|
-
never;
|
|
475
485
|
};
|
|
476
486
|
export type CogsInitialState<T> =
|
|
477
487
|
| {
|
|
@@ -796,18 +806,21 @@ export const createCogsState = <
|
|
|
796
806
|
const thiState =
|
|
797
807
|
getShadowValue(stateKey as string, []) || statePart[stateKey as string];
|
|
798
808
|
|
|
799
|
-
const updater = useCogsStateFn<(typeof statePart)[StateKey]>(
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
809
|
+
const updater = useCogsStateFn<(typeof statePart)[StateKey], TPlugins>(
|
|
810
|
+
thiState,
|
|
811
|
+
{
|
|
812
|
+
stateKey: stateKey as string,
|
|
813
|
+
syncUpdate: options?.syncUpdate,
|
|
814
|
+
componentId,
|
|
815
|
+
localStorage: options?.localStorage,
|
|
816
|
+
middleware: options?.middleware,
|
|
817
|
+
reactiveType: options?.reactiveType,
|
|
818
|
+
reactiveDeps: options?.reactiveDeps,
|
|
819
|
+
defaultState: options?.defaultState as any,
|
|
820
|
+
dependencies: options?.dependencies,
|
|
821
|
+
serverState: options?.serverState,
|
|
822
|
+
}
|
|
823
|
+
);
|
|
811
824
|
|
|
812
825
|
useEffect(() => {
|
|
813
826
|
if (options) {
|
|
@@ -831,62 +844,58 @@ export const createCogsState = <
|
|
|
831
844
|
|
|
832
845
|
function setCogsOptionsByKey<StateKey extends StateKeys>(
|
|
833
846
|
stateKey: StateKey,
|
|
834
|
-
options:
|
|
847
|
+
options: CreateStateOptionsType<(typeof statePart)[StateKey], TPlugins> &
|
|
848
|
+
Omit<
|
|
849
|
+
OptionsType<(typeof statePart)[StateKey]>,
|
|
850
|
+
keyof CreateStateOptionsType
|
|
851
|
+
>
|
|
835
852
|
) {
|
|
836
|
-
setOptions({ stateKey, options, initialOptionsPart });
|
|
853
|
+
setOptions({ stateKey, options, initialOptionsPart } as any);
|
|
837
854
|
|
|
838
855
|
if (options.localStorage) {
|
|
839
856
|
loadAndApplyLocalStorage(stateKey as string, options);
|
|
840
857
|
}
|
|
858
|
+
if (options.formElements) {
|
|
859
|
+
const currentPlugins = pluginStore.getState().registeredPlugins;
|
|
860
|
+
|
|
861
|
+
const updatedPlugins = currentPlugins.map((plugin) => {
|
|
862
|
+
// Use `options.formElements` as the source
|
|
863
|
+
if (options.formElements!.hasOwnProperty(plugin.name)) {
|
|
864
|
+
return {
|
|
865
|
+
...plugin,
|
|
866
|
+
formWrapper: (options.formElements as any)[plugin.name],
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
return plugin;
|
|
870
|
+
});
|
|
841
871
|
|
|
872
|
+
pluginStore.getState().setRegisteredPlugins(updatedPlugins as any);
|
|
873
|
+
}
|
|
842
874
|
notifyComponents(stateKey as string);
|
|
843
875
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
876
|
+
function setCogsOptions(
|
|
877
|
+
// The type allows any valid options, but uses a generic `unknown` for the
|
|
878
|
+
// state type because it applies to multiple different state shapes.
|
|
879
|
+
// The `TPlugins` generic is correctly preserved.
|
|
880
|
+
globalOptions: CreateStateOptionsType<unknown, TPlugins> &
|
|
881
|
+
Omit<OptionsType<unknown>, keyof CreateStateOptionsType>
|
|
847
882
|
) {
|
|
848
|
-
// Get the
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
// Create a new array by mapping over the current plugins.
|
|
852
|
-
// This is crucial for immutability and ensuring Zustand detects the change.
|
|
853
|
-
const updatedPlugins = currentPlugins.map((plugin) => {
|
|
854
|
-
// Check if the formElements object has a wrapper for this specific plugin by name.
|
|
855
|
-
if (formElements.hasOwnProperty(plugin.name)) {
|
|
856
|
-
// If it does, return a *new* plugin object.
|
|
857
|
-
// Spread the existing plugin properties and add/overwrite the formWrapper.
|
|
858
|
-
return {
|
|
859
|
-
...plugin,
|
|
860
|
-
formWrapper: formElements[plugin.name as keyof typeof formElements],
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
// If there's no new wrapper for this plugin, return the original object.
|
|
864
|
-
return plugin;
|
|
865
|
-
});
|
|
883
|
+
// Get all the state keys that this instance manages
|
|
884
|
+
const allStateKeys = Object.keys(statePart) as StateKeys[];
|
|
866
885
|
|
|
867
|
-
//
|
|
868
|
-
// This will trigger a state update that components listening to the store will react to.
|
|
869
|
-
pluginStore.getState().setRegisteredPlugins(updatedPlugins as any);
|
|
870
|
-
|
|
871
|
-
// For good measure and consistency, we should still update the formElements
|
|
872
|
-
// in the initial options, in case any other part of the system relies on it.
|
|
873
|
-
const allStateKeys = Object.keys(statePart);
|
|
886
|
+
// Loop through every state key and apply the provided options
|
|
874
887
|
allStateKeys.forEach((key) => {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
...(existingOptions.formElements || {}),
|
|
880
|
-
...formElements,
|
|
881
|
-
},
|
|
882
|
-
};
|
|
883
|
-
setInitialStateOptions(key, finalOptions);
|
|
888
|
+
// We use `as any` here because we are intentionally applying a single
|
|
889
|
+
// generic options object to many differently-typed state slices.
|
|
890
|
+
// The internal logic of setCogsOptionsByKey handles the merging correctly.
|
|
891
|
+
setCogsOptionsByKey(key, globalOptions as any);
|
|
884
892
|
});
|
|
885
893
|
}
|
|
894
|
+
|
|
886
895
|
return {
|
|
887
896
|
useCogsState,
|
|
888
897
|
setCogsOptionsByKey,
|
|
889
|
-
|
|
898
|
+
setCogsOptions,
|
|
890
899
|
};
|
|
891
900
|
};
|
|
892
901
|
|
|
@@ -1233,11 +1242,14 @@ function handleUpdate(
|
|
|
1233
1242
|
stateKey: string,
|
|
1234
1243
|
path: string[],
|
|
1235
1244
|
payload: any
|
|
1236
|
-
): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
|
|
1245
|
+
): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } | null {
|
|
1237
1246
|
// ✅ FIX: Get the old value before the update.
|
|
1238
1247
|
const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
|
|
1239
1248
|
|
|
1240
1249
|
const newValue = isFunction(payload) ? payload(oldValue) : payload;
|
|
1250
|
+
if (isDeepEqual(oldValue, newValue)) {
|
|
1251
|
+
return null; // <-- Abort the update
|
|
1252
|
+
}
|
|
1241
1253
|
|
|
1242
1254
|
// ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
|
|
1243
1255
|
// The manual loop has been removed.
|
|
@@ -1427,6 +1439,10 @@ function createEffectiveSetState<T>(
|
|
|
1427
1439
|
result = handleCut(stateKey, path);
|
|
1428
1440
|
break;
|
|
1429
1441
|
}
|
|
1442
|
+
|
|
1443
|
+
if (result === null) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1430
1446
|
result.stateKey = stateKey;
|
|
1431
1447
|
result.path = path;
|
|
1432
1448
|
updateBatchQueue.push(result);
|
|
@@ -1464,7 +1480,10 @@ function createEffectiveSetState<T>(
|
|
|
1464
1480
|
}
|
|
1465
1481
|
}
|
|
1466
1482
|
|
|
1467
|
-
export function useCogsStateFn<
|
|
1483
|
+
export function useCogsStateFn<
|
|
1484
|
+
TStateObject extends unknown,
|
|
1485
|
+
const TPlugins extends readonly CogsPlugin<any, any, any, any, any>[],
|
|
1486
|
+
>(
|
|
1468
1487
|
stateObject: TStateObject,
|
|
1469
1488
|
{
|
|
1470
1489
|
stateKey,
|
|
@@ -1774,7 +1793,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1774
1793
|
}
|
|
1775
1794
|
|
|
1776
1795
|
const updaterFinal = useMemo(() => {
|
|
1777
|
-
const handler = createProxyHandler<TStateObject>(
|
|
1796
|
+
const handler = createProxyHandler<TStateObject, TPlugins>(
|
|
1778
1797
|
thisKey,
|
|
1779
1798
|
effectiveSetState,
|
|
1780
1799
|
componentIdRef.current,
|
|
@@ -1923,12 +1942,15 @@ function getScopedData(stateKey: string, path: string[], meta?: MetaData) {
|
|
|
1923
1942
|
};
|
|
1924
1943
|
}
|
|
1925
1944
|
|
|
1926
|
-
function createProxyHandler<
|
|
1945
|
+
function createProxyHandler<
|
|
1946
|
+
T,
|
|
1947
|
+
const TPlugins extends readonly CogsPlugin<any, any, any, any, any>[],
|
|
1948
|
+
>(
|
|
1927
1949
|
stateKey: string,
|
|
1928
1950
|
effectiveSetState: EffectiveSetState<T>,
|
|
1929
1951
|
outerComponentId: string,
|
|
1930
1952
|
sessionId?: string
|
|
1931
|
-
): StateObject<T> {
|
|
1953
|
+
): StateObject<T, TPlugins> {
|
|
1932
1954
|
const proxyCache = new Map<string, any>();
|
|
1933
1955
|
|
|
1934
1956
|
function rebuildStateShape({
|
|
@@ -1951,15 +1973,31 @@ function createProxyHandler<T>(
|
|
|
1951
1973
|
}
|
|
1952
1974
|
const stateKeyPathKey = [stateKey, ...path].join('.');
|
|
1953
1975
|
|
|
1954
|
-
|
|
1955
|
-
// This is a placeholder for the proxy.
|
|
1976
|
+
const proxyTarget = () => {};
|
|
1956
1977
|
|
|
1957
1978
|
const handler = {
|
|
1958
|
-
|
|
1979
|
+
apply(target: any, thisArg: any, args: any) {
|
|
1980
|
+
if (args.length === 0) {
|
|
1981
|
+
// FIX: Calculate viewIds from meta so filters/sorts are respected
|
|
1982
|
+
const arrayPathKey = path.length > 0 ? path.join('.') : 'root';
|
|
1983
|
+
const viewIds = meta?.arrayViews?.[arrayPathKey];
|
|
1984
|
+
|
|
1985
|
+
// Pass viewIds to getShadowValue to get the filtered/sorted data
|
|
1986
|
+
return getShadowValue(stateKey, path, viewIds);
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
// Setter: state.count(5)
|
|
1990
|
+
const newValue = args[0];
|
|
1991
|
+
effectiveSetState(newValue, path, { updateType: 'update' });
|
|
1992
|
+
return true;
|
|
1993
|
+
},
|
|
1994
|
+
|
|
1995
|
+
get(target: any, prop: string, receiver: any) {
|
|
1996
|
+
if (prop === 'call' || prop === 'apply' || prop === 'bind') {
|
|
1997
|
+
return Reflect.get(target, prop, receiver);
|
|
1998
|
+
}
|
|
1999
|
+
|
|
1959
2000
|
if (typeof prop !== 'string') {
|
|
1960
|
-
// This is a Symbol. Let the default "get" behavior happen.
|
|
1961
|
-
// This allows internal JS operations and dev tools to work correctly
|
|
1962
|
-
// without interfering with your state logic.
|
|
1963
2001
|
return Reflect.get(target, prop);
|
|
1964
2002
|
}
|
|
1965
2003
|
if (path.length === 0 && prop in rootLevelMethods) {
|
|
@@ -2044,7 +2082,7 @@ function createProxyHandler<T>(
|
|
|
2044
2082
|
const getStatusFunc = () => {
|
|
2045
2083
|
// ✅ Use the optimized helper to get all data in one efficient call
|
|
2046
2084
|
const { shadowMeta, value } = getScopedData(stateKey, path, meta);
|
|
2047
|
-
|
|
2085
|
+
|
|
2048
2086
|
if (shadowMeta?.isDirty === true) {
|
|
2049
2087
|
return 'dirty';
|
|
2050
2088
|
}
|
|
@@ -2087,6 +2125,75 @@ function createProxyHandler<T>(
|
|
|
2087
2125
|
if (storageKey) localStorage.removeItem(storageKey);
|
|
2088
2126
|
};
|
|
2089
2127
|
}
|
|
2128
|
+
|
|
2129
|
+
if (prop === '$validate') {
|
|
2130
|
+
return () => {
|
|
2131
|
+
const store = getGlobalStore.getState();
|
|
2132
|
+
// 1. Get current data and schema
|
|
2133
|
+
const { value } = getScopedData(stateKey, path, meta);
|
|
2134
|
+
const opts = store.getInitialOptions(stateKey);
|
|
2135
|
+
const schema =
|
|
2136
|
+
opts?.validation?.zodSchemaV4 || opts?.validation?.zodSchemaV3;
|
|
2137
|
+
|
|
2138
|
+
if (!schema) {
|
|
2139
|
+
return { success: true, data: value };
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
// 2. Run Zod
|
|
2143
|
+
const result = (schema as any).safeParse(value);
|
|
2144
|
+
|
|
2145
|
+
// 3. Clear ANY existing errors for this path first (reset state)
|
|
2146
|
+
// You might want to be smarter about this for nested objects,
|
|
2147
|
+
// but effectively we need to wipe previous red borders before applying new ones.
|
|
2148
|
+
// (Using the logic from $clearZodValidation)
|
|
2149
|
+
const clearPath = (currentPath: string[]) => {
|
|
2150
|
+
const currentMeta =
|
|
2151
|
+
store.getShadowMetadata(stateKey, currentPath) || {};
|
|
2152
|
+
store.setShadowMetadata(stateKey, currentPath, {
|
|
2153
|
+
...currentMeta,
|
|
2154
|
+
validation: {
|
|
2155
|
+
status: 'NOT_VALIDATED',
|
|
2156
|
+
errors: [],
|
|
2157
|
+
lastValidated: Date.now(),
|
|
2158
|
+
},
|
|
2159
|
+
});
|
|
2160
|
+
};
|
|
2161
|
+
// Note: Ideally you recursively clear errors here, but for now we proceed to add new ones.
|
|
2162
|
+
|
|
2163
|
+
// 4. If Invalid, apply errors to State (This turns the UI red)
|
|
2164
|
+
if (!result.success) {
|
|
2165
|
+
result.error.errors.forEach((error: any) => {
|
|
2166
|
+
// Calculate the exact path to the field with the error
|
|
2167
|
+
const errorPath = [...path, ...error.path.map(String)];
|
|
2168
|
+
|
|
2169
|
+
const currentMeta =
|
|
2170
|
+
store.getShadowMetadata(stateKey, errorPath) || {};
|
|
2171
|
+
|
|
2172
|
+
store.setShadowMetadata(stateKey, errorPath, {
|
|
2173
|
+
...currentMeta,
|
|
2174
|
+
validation: {
|
|
2175
|
+
status: 'INVALID',
|
|
2176
|
+
errors: [
|
|
2177
|
+
{
|
|
2178
|
+
source: 'client',
|
|
2179
|
+
message: error.message,
|
|
2180
|
+
severity: 'error',
|
|
2181
|
+
code: error.code,
|
|
2182
|
+
},
|
|
2183
|
+
],
|
|
2184
|
+
lastValidated: Date.now(),
|
|
2185
|
+
validatedValue: getShadowValue(stateKey, errorPath),
|
|
2186
|
+
},
|
|
2187
|
+
});
|
|
2188
|
+
});
|
|
2189
|
+
|
|
2190
|
+
// Notify components to re-render and show the errors
|
|
2191
|
+
notifyComponents(stateKey);
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
return result;
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2090
2197
|
if (prop === '$showValidationErrors') {
|
|
2091
2198
|
return () => {
|
|
2092
2199
|
const { shadowMeta } = getScopedData(stateKey, path, meta);
|
|
@@ -2181,357 +2288,7 @@ function createProxyHandler<T>(
|
|
|
2181
2288
|
};
|
|
2182
2289
|
}
|
|
2183
2290
|
|
|
2184
|
-
if (prop === '$
|
|
2185
|
-
return (
|
|
2186
|
-
options: VirtualViewOptions
|
|
2187
|
-
): VirtualStateObjectResult<any[]> => {
|
|
2188
|
-
const {
|
|
2189
|
-
itemHeight = 50,
|
|
2190
|
-
overscan = 6,
|
|
2191
|
-
stickToBottom = false,
|
|
2192
|
-
scrollStickTolerance = 75,
|
|
2193
|
-
} = options;
|
|
2194
|
-
|
|
2195
|
-
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
2196
|
-
const [range, setRange] = useState({
|
|
2197
|
-
startIndex: 0,
|
|
2198
|
-
endIndex: 10,
|
|
2199
|
-
});
|
|
2200
|
-
const [rerender, forceUpdate] = useState({});
|
|
2201
|
-
const initialScrollRef = useRef(true);
|
|
2202
|
-
|
|
2203
|
-
useEffect(() => {
|
|
2204
|
-
const interval = setInterval(() => {
|
|
2205
|
-
forceUpdate({});
|
|
2206
|
-
}, 1000);
|
|
2207
|
-
return () => clearInterval(interval);
|
|
2208
|
-
}, []);
|
|
2209
|
-
|
|
2210
|
-
// Scroll state management
|
|
2211
|
-
const scrollStateRef = useRef({
|
|
2212
|
-
isUserScrolling: false,
|
|
2213
|
-
lastScrollTop: 0,
|
|
2214
|
-
scrollUpCount: 0,
|
|
2215
|
-
isNearBottom: true,
|
|
2216
|
-
});
|
|
2217
|
-
|
|
2218
|
-
// Measurement cache
|
|
2219
|
-
const measurementCache = useRef(
|
|
2220
|
-
new Map<string, { height: number; offset: number }>()
|
|
2221
|
-
);
|
|
2222
|
-
const { keys: arrayKeys } = getArrayData(stateKey, path, meta);
|
|
2223
|
-
|
|
2224
|
-
// Subscribe to state changes like stateList does
|
|
2225
|
-
useEffect(() => {
|
|
2226
|
-
const stateKeyPathKey = [stateKey, ...path].join('.');
|
|
2227
|
-
const unsubscribe = getGlobalStore
|
|
2228
|
-
.getState()
|
|
2229
|
-
.subscribeToPath(stateKeyPathKey, (e) => {
|
|
2230
|
-
if (e.type === 'GET_SELECTED') {
|
|
2231
|
-
return;
|
|
2232
|
-
}
|
|
2233
|
-
if (e.type === 'SERVER_STATE_UPDATE') {
|
|
2234
|
-
// forceUpdate({});
|
|
2235
|
-
}
|
|
2236
|
-
});
|
|
2237
|
-
|
|
2238
|
-
return () => {
|
|
2239
|
-
unsubscribe();
|
|
2240
|
-
};
|
|
2241
|
-
}, [componentId, stateKey, path.join('.')]);
|
|
2242
|
-
|
|
2243
|
-
// YOUR ORIGINAL INITIAL POSITIONING - KEEPING EXACTLY AS IS
|
|
2244
|
-
useLayoutEffect(() => {
|
|
2245
|
-
if (
|
|
2246
|
-
stickToBottom &&
|
|
2247
|
-
arrayKeys.length > 0 &&
|
|
2248
|
-
containerRef.current &&
|
|
2249
|
-
!scrollStateRef.current.isUserScrolling &&
|
|
2250
|
-
initialScrollRef.current
|
|
2251
|
-
) {
|
|
2252
|
-
const container = containerRef.current;
|
|
2253
|
-
|
|
2254
|
-
const waitForContainer = () => {
|
|
2255
|
-
if (container.clientHeight > 0) {
|
|
2256
|
-
const visibleCount = Math.ceil(
|
|
2257
|
-
container.clientHeight / itemHeight
|
|
2258
|
-
);
|
|
2259
|
-
const endIndex = arrayKeys.length - 1;
|
|
2260
|
-
const startIndex = Math.max(
|
|
2261
|
-
0,
|
|
2262
|
-
endIndex - visibleCount - overscan
|
|
2263
|
-
);
|
|
2264
|
-
|
|
2265
|
-
setRange({ startIndex, endIndex });
|
|
2266
|
-
|
|
2267
|
-
requestAnimationFrame(() => {
|
|
2268
|
-
scrollToBottom('instant');
|
|
2269
|
-
initialScrollRef.current = false;
|
|
2270
|
-
});
|
|
2271
|
-
} else {
|
|
2272
|
-
requestAnimationFrame(waitForContainer);
|
|
2273
|
-
}
|
|
2274
|
-
};
|
|
2275
|
-
|
|
2276
|
-
waitForContainer();
|
|
2277
|
-
}
|
|
2278
|
-
}, [arrayKeys.length, stickToBottom, itemHeight, overscan]);
|
|
2279
|
-
|
|
2280
|
-
const rangeRef = useRef(range);
|
|
2281
|
-
useLayoutEffect(() => {
|
|
2282
|
-
rangeRef.current = range;
|
|
2283
|
-
}, [range]);
|
|
2284
|
-
|
|
2285
|
-
const arrayKeysRef = useRef(arrayKeys);
|
|
2286
|
-
useLayoutEffect(() => {
|
|
2287
|
-
arrayKeysRef.current = arrayKeys;
|
|
2288
|
-
}, [arrayKeys]);
|
|
2289
|
-
|
|
2290
|
-
const handleScroll = useCallback(() => {
|
|
2291
|
-
const container = containerRef.current;
|
|
2292
|
-
if (!container) return;
|
|
2293
|
-
|
|
2294
|
-
const currentScrollTop = container.scrollTop;
|
|
2295
|
-
const { scrollHeight, clientHeight } = container;
|
|
2296
|
-
const scrollState = scrollStateRef.current;
|
|
2297
|
-
|
|
2298
|
-
// Check if user is near bottom
|
|
2299
|
-
const distanceFromBottom =
|
|
2300
|
-
scrollHeight - (currentScrollTop + clientHeight);
|
|
2301
|
-
const wasNearBottom = scrollState.isNearBottom;
|
|
2302
|
-
scrollState.isNearBottom =
|
|
2303
|
-
distanceFromBottom <= scrollStickTolerance;
|
|
2304
|
-
|
|
2305
|
-
// Detect scroll direction
|
|
2306
|
-
if (currentScrollTop < scrollState.lastScrollTop) {
|
|
2307
|
-
// User scrolled up
|
|
2308
|
-
scrollState.scrollUpCount++;
|
|
2309
|
-
|
|
2310
|
-
if (scrollState.scrollUpCount > 3 && wasNearBottom) {
|
|
2311
|
-
// User has deliberately scrolled away from bottom
|
|
2312
|
-
scrollState.isUserScrolling = true;
|
|
2313
|
-
console.log('User scrolled away from bottom');
|
|
2314
|
-
}
|
|
2315
|
-
} else if (scrollState.isNearBottom) {
|
|
2316
|
-
// Reset if we're back near the bottom
|
|
2317
|
-
scrollState.isUserScrolling = false;
|
|
2318
|
-
scrollState.scrollUpCount = 0;
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
scrollState.lastScrollTop = currentScrollTop;
|
|
2322
|
-
|
|
2323
|
-
// Update visible range
|
|
2324
|
-
let newStartIndex = 0;
|
|
2325
|
-
for (let i = 0; i < arrayKeys.length; i++) {
|
|
2326
|
-
const itemKey = arrayKeys[i];
|
|
2327
|
-
const item = measurementCache.current.get(itemKey!);
|
|
2328
|
-
if (item && item.offset + item.height > currentScrollTop) {
|
|
2329
|
-
newStartIndex = i;
|
|
2330
|
-
break;
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
console.log(
|
|
2334
|
-
'hadnlescroll ',
|
|
2335
|
-
measurementCache.current,
|
|
2336
|
-
newStartIndex,
|
|
2337
|
-
range
|
|
2338
|
-
);
|
|
2339
|
-
// Only update if range actually changed
|
|
2340
|
-
if (newStartIndex !== range.startIndex && range.startIndex != 0) {
|
|
2341
|
-
const visibleCount = Math.ceil(clientHeight / itemHeight);
|
|
2342
|
-
setRange({
|
|
2343
|
-
startIndex: Math.max(0, newStartIndex - overscan),
|
|
2344
|
-
endIndex: Math.min(
|
|
2345
|
-
arrayKeys.length - 1,
|
|
2346
|
-
newStartIndex + visibleCount + overscan
|
|
2347
|
-
),
|
|
2348
|
-
});
|
|
2349
|
-
}
|
|
2350
|
-
}, [
|
|
2351
|
-
arrayKeys.length,
|
|
2352
|
-
range.startIndex,
|
|
2353
|
-
itemHeight,
|
|
2354
|
-
overscan,
|
|
2355
|
-
scrollStickTolerance,
|
|
2356
|
-
]);
|
|
2357
|
-
|
|
2358
|
-
// Set up scroll listener
|
|
2359
|
-
useEffect(() => {
|
|
2360
|
-
const container = containerRef.current;
|
|
2361
|
-
if (!container) return;
|
|
2362
|
-
|
|
2363
|
-
container.addEventListener('scroll', handleScroll, {
|
|
2364
|
-
passive: true,
|
|
2365
|
-
});
|
|
2366
|
-
return () => {
|
|
2367
|
-
container.removeEventListener('scroll', handleScroll);
|
|
2368
|
-
};
|
|
2369
|
-
}, [handleScroll, stickToBottom]);
|
|
2370
|
-
|
|
2371
|
-
// YOUR ORIGINAL SCROLL TO BOTTOM FUNCTION - KEEPING EXACTLY AS IS
|
|
2372
|
-
const scrollToBottom = useCallback(
|
|
2373
|
-
(behavior: ScrollBehavior = 'smooth') => {
|
|
2374
|
-
const container = containerRef.current;
|
|
2375
|
-
if (!container) return;
|
|
2376
|
-
|
|
2377
|
-
scrollStateRef.current.isUserScrolling = false;
|
|
2378
|
-
scrollStateRef.current.isNearBottom = true;
|
|
2379
|
-
scrollStateRef.current.scrollUpCount = 0;
|
|
2380
|
-
|
|
2381
|
-
const performScroll = () => {
|
|
2382
|
-
const attemptScroll = (attempts = 0) => {
|
|
2383
|
-
if (attempts > 5) return;
|
|
2384
|
-
|
|
2385
|
-
const currentHeight = container.scrollHeight;
|
|
2386
|
-
const currentScroll = container.scrollTop;
|
|
2387
|
-
const clientHeight = container.clientHeight;
|
|
2388
|
-
|
|
2389
|
-
if (currentScroll + clientHeight >= currentHeight - 1) {
|
|
2390
|
-
return;
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
container.scrollTo({
|
|
2394
|
-
top: currentHeight,
|
|
2395
|
-
behavior: behavior,
|
|
2396
|
-
});
|
|
2397
|
-
|
|
2398
|
-
setTimeout(() => {
|
|
2399
|
-
const newHeight = container.scrollHeight;
|
|
2400
|
-
const newScroll = container.scrollTop;
|
|
2401
|
-
|
|
2402
|
-
if (
|
|
2403
|
-
newHeight !== currentHeight ||
|
|
2404
|
-
newScroll + clientHeight < newHeight - 1
|
|
2405
|
-
) {
|
|
2406
|
-
attemptScroll(attempts + 1);
|
|
2407
|
-
}
|
|
2408
|
-
}, 50);
|
|
2409
|
-
};
|
|
2410
|
-
|
|
2411
|
-
attemptScroll();
|
|
2412
|
-
};
|
|
2413
|
-
|
|
2414
|
-
if ('requestIdleCallback' in window) {
|
|
2415
|
-
requestIdleCallback(performScroll, { timeout: 100 });
|
|
2416
|
-
} else {
|
|
2417
|
-
requestAnimationFrame(() => {
|
|
2418
|
-
requestAnimationFrame(performScroll);
|
|
2419
|
-
});
|
|
2420
|
-
}
|
|
2421
|
-
},
|
|
2422
|
-
[]
|
|
2423
|
-
);
|
|
2424
|
-
|
|
2425
|
-
// YOUR ORIGINAL AUTO-SCROLL EFFECTS - KEEPING ALL OF THEM
|
|
2426
|
-
useEffect(() => {
|
|
2427
|
-
if (!stickToBottom || !containerRef.current) return;
|
|
2428
|
-
|
|
2429
|
-
const container = containerRef.current;
|
|
2430
|
-
const scrollState = scrollStateRef.current;
|
|
2431
|
-
|
|
2432
|
-
let scrollTimeout: NodeJS.Timeout;
|
|
2433
|
-
const debouncedScrollToBottom = () => {
|
|
2434
|
-
clearTimeout(scrollTimeout);
|
|
2435
|
-
scrollTimeout = setTimeout(() => {
|
|
2436
|
-
if (
|
|
2437
|
-
!scrollState.isUserScrolling &&
|
|
2438
|
-
scrollState.isNearBottom
|
|
2439
|
-
) {
|
|
2440
|
-
scrollToBottom(
|
|
2441
|
-
initialScrollRef.current ? 'instant' : 'smooth'
|
|
2442
|
-
);
|
|
2443
|
-
}
|
|
2444
|
-
}, 100);
|
|
2445
|
-
};
|
|
2446
|
-
|
|
2447
|
-
const observer = new MutationObserver(() => {
|
|
2448
|
-
if (!scrollState.isUserScrolling) {
|
|
2449
|
-
debouncedScrollToBottom();
|
|
2450
|
-
}
|
|
2451
|
-
});
|
|
2452
|
-
|
|
2453
|
-
observer.observe(container, {
|
|
2454
|
-
childList: true,
|
|
2455
|
-
subtree: true,
|
|
2456
|
-
attributes: true,
|
|
2457
|
-
attributeFilter: ['style', 'class'],
|
|
2458
|
-
});
|
|
2459
|
-
|
|
2460
|
-
if (initialScrollRef.current) {
|
|
2461
|
-
setTimeout(() => {
|
|
2462
|
-
scrollToBottom('instant');
|
|
2463
|
-
}, 0);
|
|
2464
|
-
} else {
|
|
2465
|
-
debouncedScrollToBottom();
|
|
2466
|
-
}
|
|
2467
|
-
|
|
2468
|
-
return () => {
|
|
2469
|
-
clearTimeout(scrollTimeout);
|
|
2470
|
-
observer.disconnect();
|
|
2471
|
-
};
|
|
2472
|
-
}, [stickToBottom, arrayKeys.length, scrollToBottom]);
|
|
2473
|
-
|
|
2474
|
-
// Create virtual state - NO NEED to get values, only IDs!
|
|
2475
|
-
const virtualState = useMemo(() => {
|
|
2476
|
-
// 2. Physically slice the corresponding keys.
|
|
2477
|
-
const slicedKeys = Array.isArray(arrayKeys)
|
|
2478
|
-
? arrayKeys.slice(range.startIndex, range.endIndex + 1)
|
|
2479
|
-
: [];
|
|
2480
|
-
|
|
2481
|
-
// Use the same keying as getArrayData (empty string for root)
|
|
2482
|
-
const arrayPath = path.length > 0 ? path.join('.') : 'root';
|
|
2483
|
-
return rebuildStateShape({
|
|
2484
|
-
path,
|
|
2485
|
-
componentId: componentId!,
|
|
2486
|
-
meta: {
|
|
2487
|
-
...meta,
|
|
2488
|
-
arrayViews: { [arrayPath]: slicedKeys },
|
|
2489
|
-
serverStateIsUpStream: true,
|
|
2490
|
-
},
|
|
2491
|
-
});
|
|
2492
|
-
}, [range.startIndex, range.endIndex, arrayKeys, meta]);
|
|
2493
|
-
|
|
2494
|
-
return {
|
|
2495
|
-
virtualState,
|
|
2496
|
-
virtualizerProps: {
|
|
2497
|
-
outer: {
|
|
2498
|
-
ref: containerRef,
|
|
2499
|
-
style: {
|
|
2500
|
-
overflowY: 'auto' as const,
|
|
2501
|
-
height: '100%',
|
|
2502
|
-
position: 'relative' as const,
|
|
2503
|
-
},
|
|
2504
|
-
},
|
|
2505
|
-
inner: {
|
|
2506
|
-
style: {
|
|
2507
|
-
position: 'relative' as const,
|
|
2508
|
-
},
|
|
2509
|
-
},
|
|
2510
|
-
list: {
|
|
2511
|
-
style: {
|
|
2512
|
-
transform: `translateY(${
|
|
2513
|
-
measurementCache.current.get(arrayKeys[range.startIndex]!)
|
|
2514
|
-
?.offset || 0
|
|
2515
|
-
}px)`,
|
|
2516
|
-
},
|
|
2517
|
-
},
|
|
2518
|
-
},
|
|
2519
|
-
scrollToBottom,
|
|
2520
|
-
scrollToIndex: (
|
|
2521
|
-
index: number,
|
|
2522
|
-
behavior: ScrollBehavior = 'smooth'
|
|
2523
|
-
) => {
|
|
2524
|
-
if (containerRef.current && arrayKeys[index]) {
|
|
2525
|
-
const offset =
|
|
2526
|
-
measurementCache.current.get(arrayKeys[index]!)?.offset ||
|
|
2527
|
-
0;
|
|
2528
|
-
containerRef.current.scrollTo({ top: offset, behavior });
|
|
2529
|
-
}
|
|
2530
|
-
},
|
|
2531
|
-
};
|
|
2532
|
-
};
|
|
2533
|
-
}
|
|
2534
|
-
if (prop === '$stateMap') {
|
|
2291
|
+
if (prop === '$map') {
|
|
2535
2292
|
return (
|
|
2536
2293
|
callbackfn: (setter: any, index: number, arraySetter: any) => void
|
|
2537
2294
|
) => {
|
|
@@ -2571,7 +2328,7 @@ function createProxyHandler<T>(
|
|
|
2571
2328
|
};
|
|
2572
2329
|
}
|
|
2573
2330
|
|
|
2574
|
-
if (prop === '$
|
|
2331
|
+
if (prop === '$filter') {
|
|
2575
2332
|
return (callbackfn: (value: any, index: number) => boolean) => {
|
|
2576
2333
|
const arrayPathKey = path.length > 0 ? path.join('.') : 'root';
|
|
2577
2334
|
|
|
@@ -2583,7 +2340,7 @@ function createProxyHandler<T>(
|
|
|
2583
2340
|
);
|
|
2584
2341
|
|
|
2585
2342
|
if (!Array.isArray(array)) {
|
|
2586
|
-
throw new Error('
|
|
2343
|
+
throw new Error('filter can only be used on arrays');
|
|
2587
2344
|
}
|
|
2588
2345
|
|
|
2589
2346
|
// Filter the array and collect the IDs of the items that pass
|
|
@@ -2616,7 +2373,7 @@ function createProxyHandler<T>(
|
|
|
2616
2373
|
});
|
|
2617
2374
|
};
|
|
2618
2375
|
}
|
|
2619
|
-
if (prop === '$
|
|
2376
|
+
if (prop === '$sort') {
|
|
2620
2377
|
return (compareFn: (a: any, b: any) => number) => {
|
|
2621
2378
|
const arrayPathKey = path.length > 0 ? path.join('.') : 'root';
|
|
2622
2379
|
|
|
@@ -2759,7 +2516,7 @@ function createProxyHandler<T>(
|
|
|
2759
2516
|
};
|
|
2760
2517
|
}
|
|
2761
2518
|
|
|
2762
|
-
if (prop === '$
|
|
2519
|
+
if (prop === '$list') {
|
|
2763
2520
|
return (
|
|
2764
2521
|
callbackfn: (
|
|
2765
2522
|
setter: any,
|
|
@@ -2767,7 +2524,7 @@ function createProxyHandler<T>(
|
|
|
2767
2524
|
arraySetter: any
|
|
2768
2525
|
) => ReactNode
|
|
2769
2526
|
) => {
|
|
2770
|
-
const
|
|
2527
|
+
const ListWrapper = () => {
|
|
2771
2528
|
const componentIdsRef = useRef<Map<string, string>>(new Map());
|
|
2772
2529
|
|
|
2773
2530
|
const [updateTrigger, forceUpdate] = useState({});
|
|
@@ -2877,7 +2634,7 @@ function createProxyHandler<T>(
|
|
|
2877
2634
|
return <>{returnValue}</>;
|
|
2878
2635
|
};
|
|
2879
2636
|
|
|
2880
|
-
return <
|
|
2637
|
+
return <ListWrapper />;
|
|
2881
2638
|
};
|
|
2882
2639
|
}
|
|
2883
2640
|
if (prop === '$stateFlattenOn') {
|
|
@@ -3159,54 +2916,6 @@ function createProxyHandler<T>(
|
|
|
3159
2916
|
});
|
|
3160
2917
|
}
|
|
3161
2918
|
|
|
3162
|
-
// if (prop === '$formInput') {
|
|
3163
|
-
// const _getFormElement = (path: string[]): HTMLElement | null => {
|
|
3164
|
-
// const metadata = getShadowMetadata(stateKey, path);
|
|
3165
|
-
// if (metadata?.formRef?.current) {
|
|
3166
|
-
// return metadata.formRef.current;
|
|
3167
|
-
// }
|
|
3168
|
-
// // This warning is helpful for debugging if a ref is missing.
|
|
3169
|
-
// console.warn(
|
|
3170
|
-
// `Form element ref not found for stateKey "${stateKey}" at path "${path.join('.')}"`
|
|
3171
|
-
// );
|
|
3172
|
-
// return null;
|
|
3173
|
-
// };
|
|
3174
|
-
// return {
|
|
3175
|
-
// setDisabled: (isDisabled: boolean) => {
|
|
3176
|
-
// const element = _getFormElement(path) as HTMLInputElement | null;
|
|
3177
|
-
// if (element) {
|
|
3178
|
-
// element.disabled = isDisabled;
|
|
3179
|
-
// }
|
|
3180
|
-
// },
|
|
3181
|
-
// focus: () => {
|
|
3182
|
-
// const element = _getFormElement(path);
|
|
3183
|
-
// element?.focus();
|
|
3184
|
-
// },
|
|
3185
|
-
// blur: () => {
|
|
3186
|
-
// const element = _getFormElement(path);
|
|
3187
|
-
// element?.blur();
|
|
3188
|
-
// },
|
|
3189
|
-
// scrollIntoView: (options?: ScrollIntoViewOptions) => {
|
|
3190
|
-
// const element = _getFormElement(path);
|
|
3191
|
-
// element?.scrollIntoView(
|
|
3192
|
-
// options ?? { behavior: 'smooth', block: 'center' }
|
|
3193
|
-
// );
|
|
3194
|
-
// },
|
|
3195
|
-
// click: () => {
|
|
3196
|
-
// const element = _getFormElement(path);
|
|
3197
|
-
// element?.click();
|
|
3198
|
-
// },
|
|
3199
|
-
// selectText: () => {
|
|
3200
|
-
// const element = _getFormElement(path) as
|
|
3201
|
-
// | HTMLInputElement
|
|
3202
|
-
// | HTMLTextAreaElement
|
|
3203
|
-
// | null;
|
|
3204
|
-
// if (element && typeof element.select === 'function') {
|
|
3205
|
-
// element.select();
|
|
3206
|
-
// }
|
|
3207
|
-
// },
|
|
3208
|
-
// };
|
|
3209
|
-
// }
|
|
3210
2919
|
if (prop === '$$get') {
|
|
3211
2920
|
return () =>
|
|
3212
2921
|
$cogsSignal({ _stateKey: stateKey, _path: path, _meta: meta });
|
|
@@ -3374,33 +3083,6 @@ function createProxyHandler<T>(
|
|
|
3374
3083
|
},
|
|
3375
3084
|
metaData?: Record<string, any>
|
|
3376
3085
|
) => {
|
|
3377
|
-
// const validationErrorsFromServer = operation.validation || [];
|
|
3378
|
-
|
|
3379
|
-
// if (!operation || !operation.path) {
|
|
3380
|
-
// console.error(
|
|
3381
|
-
// 'Invalid operation received by $applyOperation:',
|
|
3382
|
-
// operation
|
|
3383
|
-
// );
|
|
3384
|
-
// return;
|
|
3385
|
-
// }
|
|
3386
|
-
|
|
3387
|
-
// const newErrors: ValidationError[] =
|
|
3388
|
-
// validationErrorsFromServer.map((err) => ({
|
|
3389
|
-
// source: 'sync_engine',
|
|
3390
|
-
// message: err.message,
|
|
3391
|
-
// severity: 'warning',
|
|
3392
|
-
// code: err.code,
|
|
3393
|
-
// }));
|
|
3394
|
-
// console.log('newErrors', newErrors);
|
|
3395
|
-
// getGlobalStore
|
|
3396
|
-
// .getState()
|
|
3397
|
-
// .setShadowMetadata(stateKey, operation.path, {
|
|
3398
|
-
// validation: {
|
|
3399
|
-
// status: newErrors.length > 0 ? 'INVALID' : 'VALID',
|
|
3400
|
-
// errors: newErrors,
|
|
3401
|
-
// lastValidated: Date.now(),
|
|
3402
|
-
// },
|
|
3403
|
-
// });
|
|
3404
3086
|
console.log(
|
|
3405
3087
|
'getGlobalStore',
|
|
3406
3088
|
getGlobalStore
|
|
@@ -3626,12 +3308,13 @@ function createProxyHandler<T>(
|
|
|
3626
3308
|
},
|
|
3627
3309
|
};
|
|
3628
3310
|
|
|
3629
|
-
const proxyInstance = new Proxy(
|
|
3311
|
+
const proxyInstance = new Proxy(proxyTarget, handler);
|
|
3630
3312
|
proxyCache.set(cacheKey, proxyInstance);
|
|
3631
3313
|
|
|
3632
3314
|
return proxyInstance;
|
|
3633
3315
|
}
|
|
3634
3316
|
|
|
3317
|
+
// ... (rest of the function: rootLevelMethods, returnShape, etc.)
|
|
3635
3318
|
const rootLevelMethods = {
|
|
3636
3319
|
$revertToInitialState: (obj?: { validationKey?: string }) => {
|
|
3637
3320
|
const shadowMeta = getGlobalStore
|