cogsbox-state 0.5.473 → 0.5.475-canary.0
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 +59 -29
- package/dist/CogsState.d.ts +26 -17
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +455 -452
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.jsx +250 -223
- package/dist/Components.jsx.map +1 -1
- package/dist/PluginRunner.d.ts.map +1 -1
- package/dist/PluginRunner.jsx +73 -64
- package/dist/PluginRunner.jsx.map +1 -1
- package/dist/index.js +17 -16
- package/dist/pluginStore.d.ts +56 -18
- package/dist/pluginStore.d.ts.map +1 -1
- package/dist/pluginStore.js.map +1 -1
- package/dist/plugins.d.ts +95 -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 +233 -551
- package/src/Components.tsx +69 -41
- package/src/PluginRunner.tsx +28 -15
- package/src/pluginStore.ts +37 -20
- package/src/plugins.ts +93 -131
- 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
|
+
$validate: () => { success: boolean; data?: T; error?: any };
|
|
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,14 +270,22 @@ 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
|
|
|
289
291
|
$_componentId: string | null;
|
|
@@ -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) {
|
|
@@ -817,7 +830,6 @@ export const createCogsState = <
|
|
|
817
830
|
}
|
|
818
831
|
}, [stateKey, options]);
|
|
819
832
|
useEffect(() => {
|
|
820
|
-
console.log('adding handler 1', stateKey, updater);
|
|
821
833
|
pluginStore
|
|
822
834
|
.getState()
|
|
823
835
|
.stateHandlers.set(stateKey as string, updater as any);
|
|
@@ -832,62 +844,58 @@ export const createCogsState = <
|
|
|
832
844
|
|
|
833
845
|
function setCogsOptionsByKey<StateKey extends StateKeys>(
|
|
834
846
|
stateKey: StateKey,
|
|
835
|
-
options:
|
|
847
|
+
options: CreateStateOptionsType<(typeof statePart)[StateKey], TPlugins> &
|
|
848
|
+
Omit<
|
|
849
|
+
OptionsType<(typeof statePart)[StateKey]>,
|
|
850
|
+
keyof CreateStateOptionsType
|
|
851
|
+
>
|
|
836
852
|
) {
|
|
837
|
-
setOptions({ stateKey, options, initialOptionsPart });
|
|
853
|
+
setOptions({ stateKey, options, initialOptionsPart } as any);
|
|
838
854
|
|
|
839
855
|
if (options.localStorage) {
|
|
840
856
|
loadAndApplyLocalStorage(stateKey as string, options);
|
|
841
857
|
}
|
|
858
|
+
if (options.formElements) {
|
|
859
|
+
const currentPlugins = pluginStore.getState().registeredPlugins;
|
|
842
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
|
+
});
|
|
871
|
+
|
|
872
|
+
pluginStore.getState().setRegisteredPlugins(updatedPlugins as any);
|
|
873
|
+
}
|
|
843
874
|
notifyComponents(stateKey as string);
|
|
844
875
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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>
|
|
848
882
|
) {
|
|
849
|
-
// Get the
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
// Create a new array by mapping over the current plugins.
|
|
853
|
-
// This is crucial for immutability and ensuring Zustand detects the change.
|
|
854
|
-
const updatedPlugins = currentPlugins.map((plugin) => {
|
|
855
|
-
// Check if the formElements object has a wrapper for this specific plugin by name.
|
|
856
|
-
if (formElements.hasOwnProperty(plugin.name)) {
|
|
857
|
-
// If it does, return a *new* plugin object.
|
|
858
|
-
// Spread the existing plugin properties and add/overwrite the formWrapper.
|
|
859
|
-
return {
|
|
860
|
-
...plugin,
|
|
861
|
-
formWrapper: formElements[plugin.name as keyof typeof formElements],
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
// If there's no new wrapper for this plugin, return the original object.
|
|
865
|
-
return plugin;
|
|
866
|
-
});
|
|
867
|
-
|
|
868
|
-
// Use the store's dedicated setter function to update the registered plugins list.
|
|
869
|
-
// This will trigger a state update that components listening to the store will react to.
|
|
870
|
-
pluginStore.getState().setRegisteredPlugins(updatedPlugins as any);
|
|
883
|
+
// Get all the state keys that this instance manages
|
|
884
|
+
const allStateKeys = Object.keys(statePart) as StateKeys[];
|
|
871
885
|
|
|
872
|
-
//
|
|
873
|
-
// in the initial options, in case any other part of the system relies on it.
|
|
874
|
-
const allStateKeys = Object.keys(statePart);
|
|
886
|
+
// Loop through every state key and apply the provided options
|
|
875
887
|
allStateKeys.forEach((key) => {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
...(existingOptions.formElements || {}),
|
|
881
|
-
...formElements,
|
|
882
|
-
},
|
|
883
|
-
};
|
|
884
|
-
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);
|
|
885
892
|
});
|
|
886
893
|
}
|
|
894
|
+
|
|
887
895
|
return {
|
|
888
896
|
useCogsState,
|
|
889
897
|
setCogsOptionsByKey,
|
|
890
|
-
|
|
898
|
+
setCogsOptions,
|
|
891
899
|
};
|
|
892
900
|
};
|
|
893
901
|
|
|
@@ -1234,11 +1242,14 @@ function handleUpdate(
|
|
|
1234
1242
|
stateKey: string,
|
|
1235
1243
|
path: string[],
|
|
1236
1244
|
payload: any
|
|
1237
|
-
): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
|
|
1245
|
+
): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } | null {
|
|
1238
1246
|
// ✅ FIX: Get the old value before the update.
|
|
1239
1247
|
const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
|
|
1240
1248
|
|
|
1241
1249
|
const newValue = isFunction(payload) ? payload(oldValue) : payload;
|
|
1250
|
+
if (isDeepEqual(oldValue, newValue)) {
|
|
1251
|
+
return null; // <-- Abort the update
|
|
1252
|
+
}
|
|
1242
1253
|
|
|
1243
1254
|
// ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
|
|
1244
1255
|
// The manual loop has been removed.
|
|
@@ -1428,6 +1439,10 @@ function createEffectiveSetState<T>(
|
|
|
1428
1439
|
result = handleCut(stateKey, path);
|
|
1429
1440
|
break;
|
|
1430
1441
|
}
|
|
1442
|
+
|
|
1443
|
+
if (result === null) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1431
1446
|
result.stateKey = stateKey;
|
|
1432
1447
|
result.path = path;
|
|
1433
1448
|
updateBatchQueue.push(result);
|
|
@@ -1465,7 +1480,10 @@ function createEffectiveSetState<T>(
|
|
|
1465
1480
|
}
|
|
1466
1481
|
}
|
|
1467
1482
|
|
|
1468
|
-
export function useCogsStateFn<
|
|
1483
|
+
export function useCogsStateFn<
|
|
1484
|
+
TStateObject extends unknown,
|
|
1485
|
+
const TPlugins extends readonly CogsPlugin<any, any, any, any, any>[],
|
|
1486
|
+
>(
|
|
1469
1487
|
stateObject: TStateObject,
|
|
1470
1488
|
{
|
|
1471
1489
|
stateKey,
|
|
@@ -1775,7 +1793,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1775
1793
|
}
|
|
1776
1794
|
|
|
1777
1795
|
const updaterFinal = useMemo(() => {
|
|
1778
|
-
const handler = createProxyHandler<TStateObject>(
|
|
1796
|
+
const handler = createProxyHandler<TStateObject, TPlugins>(
|
|
1779
1797
|
thisKey,
|
|
1780
1798
|
effectiveSetState,
|
|
1781
1799
|
componentIdRef.current,
|
|
@@ -1924,12 +1942,15 @@ function getScopedData(stateKey: string, path: string[], meta?: MetaData) {
|
|
|
1924
1942
|
};
|
|
1925
1943
|
}
|
|
1926
1944
|
|
|
1927
|
-
function createProxyHandler<
|
|
1945
|
+
function createProxyHandler<
|
|
1946
|
+
T,
|
|
1947
|
+
const TPlugins extends readonly CogsPlugin<any, any, any, any, any>[],
|
|
1948
|
+
>(
|
|
1928
1949
|
stateKey: string,
|
|
1929
1950
|
effectiveSetState: EffectiveSetState<T>,
|
|
1930
1951
|
outerComponentId: string,
|
|
1931
1952
|
sessionId?: string
|
|
1932
|
-
): StateObject<T> {
|
|
1953
|
+
): StateObject<T, TPlugins> {
|
|
1933
1954
|
const proxyCache = new Map<string, any>();
|
|
1934
1955
|
|
|
1935
1956
|
function rebuildStateShape({
|
|
@@ -1952,15 +1973,31 @@ function createProxyHandler<T>(
|
|
|
1952
1973
|
}
|
|
1953
1974
|
const stateKeyPathKey = [stateKey, ...path].join('.');
|
|
1954
1975
|
|
|
1955
|
-
|
|
1956
|
-
// This is a placeholder for the proxy.
|
|
1976
|
+
const proxyTarget = () => {};
|
|
1957
1977
|
|
|
1958
1978
|
const handler = {
|
|
1959
|
-
|
|
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
|
+
|
|
1960
2000
|
if (typeof prop !== 'string') {
|
|
1961
|
-
// This is a Symbol. Let the default "get" behavior happen.
|
|
1962
|
-
// This allows internal JS operations and dev tools to work correctly
|
|
1963
|
-
// without interfering with your state logic.
|
|
1964
2001
|
return Reflect.get(target, prop);
|
|
1965
2002
|
}
|
|
1966
2003
|
if (path.length === 0 && prop in rootLevelMethods) {
|
|
@@ -2045,7 +2082,7 @@ function createProxyHandler<T>(
|
|
|
2045
2082
|
const getStatusFunc = () => {
|
|
2046
2083
|
// ✅ Use the optimized helper to get all data in one efficient call
|
|
2047
2084
|
const { shadowMeta, value } = getScopedData(stateKey, path, meta);
|
|
2048
|
-
|
|
2085
|
+
|
|
2049
2086
|
if (shadowMeta?.isDirty === true) {
|
|
2050
2087
|
return 'dirty';
|
|
2051
2088
|
}
|
|
@@ -2088,6 +2125,75 @@ function createProxyHandler<T>(
|
|
|
2088
2125
|
if (storageKey) localStorage.removeItem(storageKey);
|
|
2089
2126
|
};
|
|
2090
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
|
+
}
|
|
2091
2197
|
if (prop === '$showValidationErrors') {
|
|
2092
2198
|
return () => {
|
|
2093
2199
|
const { shadowMeta } = getScopedData(stateKey, path, meta);
|
|
@@ -2182,357 +2288,7 @@ function createProxyHandler<T>(
|
|
|
2182
2288
|
};
|
|
2183
2289
|
}
|
|
2184
2290
|
|
|
2185
|
-
if (prop === '$
|
|
2186
|
-
return (
|
|
2187
|
-
options: VirtualViewOptions
|
|
2188
|
-
): VirtualStateObjectResult<any[]> => {
|
|
2189
|
-
const {
|
|
2190
|
-
itemHeight = 50,
|
|
2191
|
-
overscan = 6,
|
|
2192
|
-
stickToBottom = false,
|
|
2193
|
-
scrollStickTolerance = 75,
|
|
2194
|
-
} = options;
|
|
2195
|
-
|
|
2196
|
-
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
2197
|
-
const [range, setRange] = useState({
|
|
2198
|
-
startIndex: 0,
|
|
2199
|
-
endIndex: 10,
|
|
2200
|
-
});
|
|
2201
|
-
const [rerender, forceUpdate] = useState({});
|
|
2202
|
-
const initialScrollRef = useRef(true);
|
|
2203
|
-
|
|
2204
|
-
useEffect(() => {
|
|
2205
|
-
const interval = setInterval(() => {
|
|
2206
|
-
forceUpdate({});
|
|
2207
|
-
}, 1000);
|
|
2208
|
-
return () => clearInterval(interval);
|
|
2209
|
-
}, []);
|
|
2210
|
-
|
|
2211
|
-
// Scroll state management
|
|
2212
|
-
const scrollStateRef = useRef({
|
|
2213
|
-
isUserScrolling: false,
|
|
2214
|
-
lastScrollTop: 0,
|
|
2215
|
-
scrollUpCount: 0,
|
|
2216
|
-
isNearBottom: true,
|
|
2217
|
-
});
|
|
2218
|
-
|
|
2219
|
-
// Measurement cache
|
|
2220
|
-
const measurementCache = useRef(
|
|
2221
|
-
new Map<string, { height: number; offset: number }>()
|
|
2222
|
-
);
|
|
2223
|
-
const { keys: arrayKeys } = getArrayData(stateKey, path, meta);
|
|
2224
|
-
|
|
2225
|
-
// Subscribe to state changes like stateList does
|
|
2226
|
-
useEffect(() => {
|
|
2227
|
-
const stateKeyPathKey = [stateKey, ...path].join('.');
|
|
2228
|
-
const unsubscribe = getGlobalStore
|
|
2229
|
-
.getState()
|
|
2230
|
-
.subscribeToPath(stateKeyPathKey, (e) => {
|
|
2231
|
-
if (e.type === 'GET_SELECTED') {
|
|
2232
|
-
return;
|
|
2233
|
-
}
|
|
2234
|
-
if (e.type === 'SERVER_STATE_UPDATE') {
|
|
2235
|
-
// forceUpdate({});
|
|
2236
|
-
}
|
|
2237
|
-
});
|
|
2238
|
-
|
|
2239
|
-
return () => {
|
|
2240
|
-
unsubscribe();
|
|
2241
|
-
};
|
|
2242
|
-
}, [componentId, stateKey, path.join('.')]);
|
|
2243
|
-
|
|
2244
|
-
// YOUR ORIGINAL INITIAL POSITIONING - KEEPING EXACTLY AS IS
|
|
2245
|
-
useLayoutEffect(() => {
|
|
2246
|
-
if (
|
|
2247
|
-
stickToBottom &&
|
|
2248
|
-
arrayKeys.length > 0 &&
|
|
2249
|
-
containerRef.current &&
|
|
2250
|
-
!scrollStateRef.current.isUserScrolling &&
|
|
2251
|
-
initialScrollRef.current
|
|
2252
|
-
) {
|
|
2253
|
-
const container = containerRef.current;
|
|
2254
|
-
|
|
2255
|
-
const waitForContainer = () => {
|
|
2256
|
-
if (container.clientHeight > 0) {
|
|
2257
|
-
const visibleCount = Math.ceil(
|
|
2258
|
-
container.clientHeight / itemHeight
|
|
2259
|
-
);
|
|
2260
|
-
const endIndex = arrayKeys.length - 1;
|
|
2261
|
-
const startIndex = Math.max(
|
|
2262
|
-
0,
|
|
2263
|
-
endIndex - visibleCount - overscan
|
|
2264
|
-
);
|
|
2265
|
-
|
|
2266
|
-
setRange({ startIndex, endIndex });
|
|
2267
|
-
|
|
2268
|
-
requestAnimationFrame(() => {
|
|
2269
|
-
scrollToBottom('instant');
|
|
2270
|
-
initialScrollRef.current = false;
|
|
2271
|
-
});
|
|
2272
|
-
} else {
|
|
2273
|
-
requestAnimationFrame(waitForContainer);
|
|
2274
|
-
}
|
|
2275
|
-
};
|
|
2276
|
-
|
|
2277
|
-
waitForContainer();
|
|
2278
|
-
}
|
|
2279
|
-
}, [arrayKeys.length, stickToBottom, itemHeight, overscan]);
|
|
2280
|
-
|
|
2281
|
-
const rangeRef = useRef(range);
|
|
2282
|
-
useLayoutEffect(() => {
|
|
2283
|
-
rangeRef.current = range;
|
|
2284
|
-
}, [range]);
|
|
2285
|
-
|
|
2286
|
-
const arrayKeysRef = useRef(arrayKeys);
|
|
2287
|
-
useLayoutEffect(() => {
|
|
2288
|
-
arrayKeysRef.current = arrayKeys;
|
|
2289
|
-
}, [arrayKeys]);
|
|
2290
|
-
|
|
2291
|
-
const handleScroll = useCallback(() => {
|
|
2292
|
-
const container = containerRef.current;
|
|
2293
|
-
if (!container) return;
|
|
2294
|
-
|
|
2295
|
-
const currentScrollTop = container.scrollTop;
|
|
2296
|
-
const { scrollHeight, clientHeight } = container;
|
|
2297
|
-
const scrollState = scrollStateRef.current;
|
|
2298
|
-
|
|
2299
|
-
// Check if user is near bottom
|
|
2300
|
-
const distanceFromBottom =
|
|
2301
|
-
scrollHeight - (currentScrollTop + clientHeight);
|
|
2302
|
-
const wasNearBottom = scrollState.isNearBottom;
|
|
2303
|
-
scrollState.isNearBottom =
|
|
2304
|
-
distanceFromBottom <= scrollStickTolerance;
|
|
2305
|
-
|
|
2306
|
-
// Detect scroll direction
|
|
2307
|
-
if (currentScrollTop < scrollState.lastScrollTop) {
|
|
2308
|
-
// User scrolled up
|
|
2309
|
-
scrollState.scrollUpCount++;
|
|
2310
|
-
|
|
2311
|
-
if (scrollState.scrollUpCount > 3 && wasNearBottom) {
|
|
2312
|
-
// User has deliberately scrolled away from bottom
|
|
2313
|
-
scrollState.isUserScrolling = true;
|
|
2314
|
-
console.log('User scrolled away from bottom');
|
|
2315
|
-
}
|
|
2316
|
-
} else if (scrollState.isNearBottom) {
|
|
2317
|
-
// Reset if we're back near the bottom
|
|
2318
|
-
scrollState.isUserScrolling = false;
|
|
2319
|
-
scrollState.scrollUpCount = 0;
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
scrollState.lastScrollTop = currentScrollTop;
|
|
2323
|
-
|
|
2324
|
-
// Update visible range
|
|
2325
|
-
let newStartIndex = 0;
|
|
2326
|
-
for (let i = 0; i < arrayKeys.length; i++) {
|
|
2327
|
-
const itemKey = arrayKeys[i];
|
|
2328
|
-
const item = measurementCache.current.get(itemKey!);
|
|
2329
|
-
if (item && item.offset + item.height > currentScrollTop) {
|
|
2330
|
-
newStartIndex = i;
|
|
2331
|
-
break;
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
console.log(
|
|
2335
|
-
'hadnlescroll ',
|
|
2336
|
-
measurementCache.current,
|
|
2337
|
-
newStartIndex,
|
|
2338
|
-
range
|
|
2339
|
-
);
|
|
2340
|
-
// Only update if range actually changed
|
|
2341
|
-
if (newStartIndex !== range.startIndex && range.startIndex != 0) {
|
|
2342
|
-
const visibleCount = Math.ceil(clientHeight / itemHeight);
|
|
2343
|
-
setRange({
|
|
2344
|
-
startIndex: Math.max(0, newStartIndex - overscan),
|
|
2345
|
-
endIndex: Math.min(
|
|
2346
|
-
arrayKeys.length - 1,
|
|
2347
|
-
newStartIndex + visibleCount + overscan
|
|
2348
|
-
),
|
|
2349
|
-
});
|
|
2350
|
-
}
|
|
2351
|
-
}, [
|
|
2352
|
-
arrayKeys.length,
|
|
2353
|
-
range.startIndex,
|
|
2354
|
-
itemHeight,
|
|
2355
|
-
overscan,
|
|
2356
|
-
scrollStickTolerance,
|
|
2357
|
-
]);
|
|
2358
|
-
|
|
2359
|
-
// Set up scroll listener
|
|
2360
|
-
useEffect(() => {
|
|
2361
|
-
const container = containerRef.current;
|
|
2362
|
-
if (!container) return;
|
|
2363
|
-
|
|
2364
|
-
container.addEventListener('scroll', handleScroll, {
|
|
2365
|
-
passive: true,
|
|
2366
|
-
});
|
|
2367
|
-
return () => {
|
|
2368
|
-
container.removeEventListener('scroll', handleScroll);
|
|
2369
|
-
};
|
|
2370
|
-
}, [handleScroll, stickToBottom]);
|
|
2371
|
-
|
|
2372
|
-
// YOUR ORIGINAL SCROLL TO BOTTOM FUNCTION - KEEPING EXACTLY AS IS
|
|
2373
|
-
const scrollToBottom = useCallback(
|
|
2374
|
-
(behavior: ScrollBehavior = 'smooth') => {
|
|
2375
|
-
const container = containerRef.current;
|
|
2376
|
-
if (!container) return;
|
|
2377
|
-
|
|
2378
|
-
scrollStateRef.current.isUserScrolling = false;
|
|
2379
|
-
scrollStateRef.current.isNearBottom = true;
|
|
2380
|
-
scrollStateRef.current.scrollUpCount = 0;
|
|
2381
|
-
|
|
2382
|
-
const performScroll = () => {
|
|
2383
|
-
const attemptScroll = (attempts = 0) => {
|
|
2384
|
-
if (attempts > 5) return;
|
|
2385
|
-
|
|
2386
|
-
const currentHeight = container.scrollHeight;
|
|
2387
|
-
const currentScroll = container.scrollTop;
|
|
2388
|
-
const clientHeight = container.clientHeight;
|
|
2389
|
-
|
|
2390
|
-
if (currentScroll + clientHeight >= currentHeight - 1) {
|
|
2391
|
-
return;
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
container.scrollTo({
|
|
2395
|
-
top: currentHeight,
|
|
2396
|
-
behavior: behavior,
|
|
2397
|
-
});
|
|
2398
|
-
|
|
2399
|
-
setTimeout(() => {
|
|
2400
|
-
const newHeight = container.scrollHeight;
|
|
2401
|
-
const newScroll = container.scrollTop;
|
|
2402
|
-
|
|
2403
|
-
if (
|
|
2404
|
-
newHeight !== currentHeight ||
|
|
2405
|
-
newScroll + clientHeight < newHeight - 1
|
|
2406
|
-
) {
|
|
2407
|
-
attemptScroll(attempts + 1);
|
|
2408
|
-
}
|
|
2409
|
-
}, 50);
|
|
2410
|
-
};
|
|
2411
|
-
|
|
2412
|
-
attemptScroll();
|
|
2413
|
-
};
|
|
2414
|
-
|
|
2415
|
-
if ('requestIdleCallback' in window) {
|
|
2416
|
-
requestIdleCallback(performScroll, { timeout: 100 });
|
|
2417
|
-
} else {
|
|
2418
|
-
requestAnimationFrame(() => {
|
|
2419
|
-
requestAnimationFrame(performScroll);
|
|
2420
|
-
});
|
|
2421
|
-
}
|
|
2422
|
-
},
|
|
2423
|
-
[]
|
|
2424
|
-
);
|
|
2425
|
-
|
|
2426
|
-
// YOUR ORIGINAL AUTO-SCROLL EFFECTS - KEEPING ALL OF THEM
|
|
2427
|
-
useEffect(() => {
|
|
2428
|
-
if (!stickToBottom || !containerRef.current) return;
|
|
2429
|
-
|
|
2430
|
-
const container = containerRef.current;
|
|
2431
|
-
const scrollState = scrollStateRef.current;
|
|
2432
|
-
|
|
2433
|
-
let scrollTimeout: NodeJS.Timeout;
|
|
2434
|
-
const debouncedScrollToBottom = () => {
|
|
2435
|
-
clearTimeout(scrollTimeout);
|
|
2436
|
-
scrollTimeout = setTimeout(() => {
|
|
2437
|
-
if (
|
|
2438
|
-
!scrollState.isUserScrolling &&
|
|
2439
|
-
scrollState.isNearBottom
|
|
2440
|
-
) {
|
|
2441
|
-
scrollToBottom(
|
|
2442
|
-
initialScrollRef.current ? 'instant' : 'smooth'
|
|
2443
|
-
);
|
|
2444
|
-
}
|
|
2445
|
-
}, 100);
|
|
2446
|
-
};
|
|
2447
|
-
|
|
2448
|
-
const observer = new MutationObserver(() => {
|
|
2449
|
-
if (!scrollState.isUserScrolling) {
|
|
2450
|
-
debouncedScrollToBottom();
|
|
2451
|
-
}
|
|
2452
|
-
});
|
|
2453
|
-
|
|
2454
|
-
observer.observe(container, {
|
|
2455
|
-
childList: true,
|
|
2456
|
-
subtree: true,
|
|
2457
|
-
attributes: true,
|
|
2458
|
-
attributeFilter: ['style', 'class'],
|
|
2459
|
-
});
|
|
2460
|
-
|
|
2461
|
-
if (initialScrollRef.current) {
|
|
2462
|
-
setTimeout(() => {
|
|
2463
|
-
scrollToBottom('instant');
|
|
2464
|
-
}, 0);
|
|
2465
|
-
} else {
|
|
2466
|
-
debouncedScrollToBottom();
|
|
2467
|
-
}
|
|
2468
|
-
|
|
2469
|
-
return () => {
|
|
2470
|
-
clearTimeout(scrollTimeout);
|
|
2471
|
-
observer.disconnect();
|
|
2472
|
-
};
|
|
2473
|
-
}, [stickToBottom, arrayKeys.length, scrollToBottom]);
|
|
2474
|
-
|
|
2475
|
-
// Create virtual state - NO NEED to get values, only IDs!
|
|
2476
|
-
const virtualState = useMemo(() => {
|
|
2477
|
-
// 2. Physically slice the corresponding keys.
|
|
2478
|
-
const slicedKeys = Array.isArray(arrayKeys)
|
|
2479
|
-
? arrayKeys.slice(range.startIndex, range.endIndex + 1)
|
|
2480
|
-
: [];
|
|
2481
|
-
|
|
2482
|
-
// Use the same keying as getArrayData (empty string for root)
|
|
2483
|
-
const arrayPath = path.length > 0 ? path.join('.') : 'root';
|
|
2484
|
-
return rebuildStateShape({
|
|
2485
|
-
path,
|
|
2486
|
-
componentId: componentId!,
|
|
2487
|
-
meta: {
|
|
2488
|
-
...meta,
|
|
2489
|
-
arrayViews: { [arrayPath]: slicedKeys },
|
|
2490
|
-
serverStateIsUpStream: true,
|
|
2491
|
-
},
|
|
2492
|
-
});
|
|
2493
|
-
}, [range.startIndex, range.endIndex, arrayKeys, meta]);
|
|
2494
|
-
|
|
2495
|
-
return {
|
|
2496
|
-
virtualState,
|
|
2497
|
-
virtualizerProps: {
|
|
2498
|
-
outer: {
|
|
2499
|
-
ref: containerRef,
|
|
2500
|
-
style: {
|
|
2501
|
-
overflowY: 'auto' as const,
|
|
2502
|
-
height: '100%',
|
|
2503
|
-
position: 'relative' as const,
|
|
2504
|
-
},
|
|
2505
|
-
},
|
|
2506
|
-
inner: {
|
|
2507
|
-
style: {
|
|
2508
|
-
position: 'relative' as const,
|
|
2509
|
-
},
|
|
2510
|
-
},
|
|
2511
|
-
list: {
|
|
2512
|
-
style: {
|
|
2513
|
-
transform: `translateY(${
|
|
2514
|
-
measurementCache.current.get(arrayKeys[range.startIndex]!)
|
|
2515
|
-
?.offset || 0
|
|
2516
|
-
}px)`,
|
|
2517
|
-
},
|
|
2518
|
-
},
|
|
2519
|
-
},
|
|
2520
|
-
scrollToBottom,
|
|
2521
|
-
scrollToIndex: (
|
|
2522
|
-
index: number,
|
|
2523
|
-
behavior: ScrollBehavior = 'smooth'
|
|
2524
|
-
) => {
|
|
2525
|
-
if (containerRef.current && arrayKeys[index]) {
|
|
2526
|
-
const offset =
|
|
2527
|
-
measurementCache.current.get(arrayKeys[index]!)?.offset ||
|
|
2528
|
-
0;
|
|
2529
|
-
containerRef.current.scrollTo({ top: offset, behavior });
|
|
2530
|
-
}
|
|
2531
|
-
},
|
|
2532
|
-
};
|
|
2533
|
-
};
|
|
2534
|
-
}
|
|
2535
|
-
if (prop === '$stateMap') {
|
|
2291
|
+
if (prop === '$map') {
|
|
2536
2292
|
return (
|
|
2537
2293
|
callbackfn: (setter: any, index: number, arraySetter: any) => void
|
|
2538
2294
|
) => {
|
|
@@ -2572,7 +2328,7 @@ function createProxyHandler<T>(
|
|
|
2572
2328
|
};
|
|
2573
2329
|
}
|
|
2574
2330
|
|
|
2575
|
-
if (prop === '$
|
|
2331
|
+
if (prop === '$filter') {
|
|
2576
2332
|
return (callbackfn: (value: any, index: number) => boolean) => {
|
|
2577
2333
|
const arrayPathKey = path.length > 0 ? path.join('.') : 'root';
|
|
2578
2334
|
|
|
@@ -2584,7 +2340,7 @@ function createProxyHandler<T>(
|
|
|
2584
2340
|
);
|
|
2585
2341
|
|
|
2586
2342
|
if (!Array.isArray(array)) {
|
|
2587
|
-
throw new Error('
|
|
2343
|
+
throw new Error('filter can only be used on arrays');
|
|
2588
2344
|
}
|
|
2589
2345
|
|
|
2590
2346
|
// Filter the array and collect the IDs of the items that pass
|
|
@@ -2617,7 +2373,7 @@ function createProxyHandler<T>(
|
|
|
2617
2373
|
});
|
|
2618
2374
|
};
|
|
2619
2375
|
}
|
|
2620
|
-
if (prop === '$
|
|
2376
|
+
if (prop === '$sort') {
|
|
2621
2377
|
return (compareFn: (a: any, b: any) => number) => {
|
|
2622
2378
|
const arrayPathKey = path.length > 0 ? path.join('.') : 'root';
|
|
2623
2379
|
|
|
@@ -2760,7 +2516,7 @@ function createProxyHandler<T>(
|
|
|
2760
2516
|
};
|
|
2761
2517
|
}
|
|
2762
2518
|
|
|
2763
|
-
if (prop === '$
|
|
2519
|
+
if (prop === '$list') {
|
|
2764
2520
|
return (
|
|
2765
2521
|
callbackfn: (
|
|
2766
2522
|
setter: any,
|
|
@@ -2768,7 +2524,7 @@ function createProxyHandler<T>(
|
|
|
2768
2524
|
arraySetter: any
|
|
2769
2525
|
) => ReactNode
|
|
2770
2526
|
) => {
|
|
2771
|
-
const
|
|
2527
|
+
const ListWrapper = () => {
|
|
2772
2528
|
const componentIdsRef = useRef<Map<string, string>>(new Map());
|
|
2773
2529
|
|
|
2774
2530
|
const [updateTrigger, forceUpdate] = useState({});
|
|
@@ -2878,7 +2634,7 @@ function createProxyHandler<T>(
|
|
|
2878
2634
|
return <>{returnValue}</>;
|
|
2879
2635
|
};
|
|
2880
2636
|
|
|
2881
|
-
return <
|
|
2637
|
+
return <ListWrapper />;
|
|
2882
2638
|
};
|
|
2883
2639
|
}
|
|
2884
2640
|
if (prop === '$stateFlattenOn') {
|
|
@@ -3160,54 +2916,6 @@ function createProxyHandler<T>(
|
|
|
3160
2916
|
});
|
|
3161
2917
|
}
|
|
3162
2918
|
|
|
3163
|
-
// if (prop === '$formInput') {
|
|
3164
|
-
// const _getFormElement = (path: string[]): HTMLElement | null => {
|
|
3165
|
-
// const metadata = getShadowMetadata(stateKey, path);
|
|
3166
|
-
// if (metadata?.formRef?.current) {
|
|
3167
|
-
// return metadata.formRef.current;
|
|
3168
|
-
// }
|
|
3169
|
-
// // This warning is helpful for debugging if a ref is missing.
|
|
3170
|
-
// console.warn(
|
|
3171
|
-
// `Form element ref not found for stateKey "${stateKey}" at path "${path.join('.')}"`
|
|
3172
|
-
// );
|
|
3173
|
-
// return null;
|
|
3174
|
-
// };
|
|
3175
|
-
// return {
|
|
3176
|
-
// setDisabled: (isDisabled: boolean) => {
|
|
3177
|
-
// const element = _getFormElement(path) as HTMLInputElement | null;
|
|
3178
|
-
// if (element) {
|
|
3179
|
-
// element.disabled = isDisabled;
|
|
3180
|
-
// }
|
|
3181
|
-
// },
|
|
3182
|
-
// focus: () => {
|
|
3183
|
-
// const element = _getFormElement(path);
|
|
3184
|
-
// element?.focus();
|
|
3185
|
-
// },
|
|
3186
|
-
// blur: () => {
|
|
3187
|
-
// const element = _getFormElement(path);
|
|
3188
|
-
// element?.blur();
|
|
3189
|
-
// },
|
|
3190
|
-
// scrollIntoView: (options?: ScrollIntoViewOptions) => {
|
|
3191
|
-
// const element = _getFormElement(path);
|
|
3192
|
-
// element?.scrollIntoView(
|
|
3193
|
-
// options ?? { behavior: 'smooth', block: 'center' }
|
|
3194
|
-
// );
|
|
3195
|
-
// },
|
|
3196
|
-
// click: () => {
|
|
3197
|
-
// const element = _getFormElement(path);
|
|
3198
|
-
// element?.click();
|
|
3199
|
-
// },
|
|
3200
|
-
// selectText: () => {
|
|
3201
|
-
// const element = _getFormElement(path) as
|
|
3202
|
-
// | HTMLInputElement
|
|
3203
|
-
// | HTMLTextAreaElement
|
|
3204
|
-
// | null;
|
|
3205
|
-
// if (element && typeof element.select === 'function') {
|
|
3206
|
-
// element.select();
|
|
3207
|
-
// }
|
|
3208
|
-
// },
|
|
3209
|
-
// };
|
|
3210
|
-
// }
|
|
3211
2919
|
if (prop === '$$get') {
|
|
3212
2920
|
return () =>
|
|
3213
2921
|
$cogsSignal({ _stateKey: stateKey, _path: path, _meta: meta });
|
|
@@ -3375,33 +3083,6 @@ function createProxyHandler<T>(
|
|
|
3375
3083
|
},
|
|
3376
3084
|
metaData?: Record<string, any>
|
|
3377
3085
|
) => {
|
|
3378
|
-
// const validationErrorsFromServer = operation.validation || [];
|
|
3379
|
-
|
|
3380
|
-
// if (!operation || !operation.path) {
|
|
3381
|
-
// console.error(
|
|
3382
|
-
// 'Invalid operation received by $applyOperation:',
|
|
3383
|
-
// operation
|
|
3384
|
-
// );
|
|
3385
|
-
// return;
|
|
3386
|
-
// }
|
|
3387
|
-
|
|
3388
|
-
// const newErrors: ValidationError[] =
|
|
3389
|
-
// validationErrorsFromServer.map((err) => ({
|
|
3390
|
-
// source: 'sync_engine',
|
|
3391
|
-
// message: err.message,
|
|
3392
|
-
// severity: 'warning',
|
|
3393
|
-
// code: err.code,
|
|
3394
|
-
// }));
|
|
3395
|
-
// console.log('newErrors', newErrors);
|
|
3396
|
-
// getGlobalStore
|
|
3397
|
-
// .getState()
|
|
3398
|
-
// .setShadowMetadata(stateKey, operation.path, {
|
|
3399
|
-
// validation: {
|
|
3400
|
-
// status: newErrors.length > 0 ? 'INVALID' : 'VALID',
|
|
3401
|
-
// errors: newErrors,
|
|
3402
|
-
// lastValidated: Date.now(),
|
|
3403
|
-
// },
|
|
3404
|
-
// });
|
|
3405
3086
|
console.log(
|
|
3406
3087
|
'getGlobalStore',
|
|
3407
3088
|
getGlobalStore
|
|
@@ -3627,12 +3308,13 @@ function createProxyHandler<T>(
|
|
|
3627
3308
|
},
|
|
3628
3309
|
};
|
|
3629
3310
|
|
|
3630
|
-
const proxyInstance = new Proxy(
|
|
3311
|
+
const proxyInstance = new Proxy(proxyTarget, handler);
|
|
3631
3312
|
proxyCache.set(cacheKey, proxyInstance);
|
|
3632
3313
|
|
|
3633
3314
|
return proxyInstance;
|
|
3634
3315
|
}
|
|
3635
3316
|
|
|
3317
|
+
// ... (rest of the function: rootLevelMethods, returnShape, etc.)
|
|
3636
3318
|
const rootLevelMethods = {
|
|
3637
3319
|
$revertToInitialState: (obj?: { validationKey?: string }) => {
|
|
3638
3320
|
const shadowMeta = getGlobalStore
|