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/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<TShape extends unknown> = {
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
- $stateSort: (
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
- $stateList: (
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
- $stateMap: <U>(
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
- $stateFind: (
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
- $stateFilter: (
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 EndType<T, IsArrayElement = false> = {
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
- $ignoreFields: (fields: string[]) => StateObject<T>;
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<T> = (T extends any[]
280
- ? ArrayEndType<T>
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?: z4.ZodType<any, any, any>;
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]>(thiState, {
800
- stateKey: stateKey as string,
801
- syncUpdate: options?.syncUpdate,
802
- componentId,
803
- localStorage: options?.localStorage,
804
- middleware: options?.middleware,
805
- reactiveType: options?.reactiveType,
806
- reactiveDeps: options?.reactiveDeps,
807
- defaultState: options?.defaultState as any,
808
- dependencies: options?.dependencies,
809
- serverState: options?.serverState,
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: OptionsType<(typeof statePart)[StateKey]>
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
- function setCogsFormElements(
847
- formElements: FormsElementsType<State, TPlugins>
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 current list of registered plugins from the store.
850
- const currentPlugins = pluginStore.getState().registeredPlugins;
851
-
852
- // Create a new array by mapping over the current plugins.
853
- // This is crucial for immutability and ensuring Zustand detects the change.
854
- const updatedPlugins = currentPlugins.map((plugin) => {
855
- // Check if the formElements object has a wrapper for this specific plugin by name.
856
- if (formElements.hasOwnProperty(plugin.name)) {
857
- // If it does, return a *new* plugin object.
858
- // Spread the existing plugin properties and add/overwrite the formWrapper.
859
- return {
860
- ...plugin,
861
- formWrapper: formElements[plugin.name as keyof typeof formElements],
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
- // For good measure and consistency, we should still update the formElements
873
- // in the initial options, in case any other part of the system relies on it.
874
- const allStateKeys = Object.keys(statePart);
886
+ // Loop through every state key and apply the provided options
875
887
  allStateKeys.forEach((key) => {
876
- const existingOptions = getInitialOptions(key) || {};
877
- const finalOptions = {
878
- ...existingOptions,
879
- formElements: {
880
- ...(existingOptions.formElements || {}),
881
- ...formElements,
882
- },
883
- };
884
- setInitialStateOptions(key, finalOptions);
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
- setCogsFormElements,
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<TStateObject extends unknown>(
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<T>(
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
- // We attach baseObj properties *inside* the get trap now to avoid recursion
1956
- // This is a placeholder for the proxy.
1976
+ const proxyTarget = () => {};
1957
1977
 
1958
1978
  const handler = {
1959
- get(target: any, prop: string) {
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
- console.log('getStatusFunc', path, shadowMeta, value);
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 === '$useVirtualView') {
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 === '$stateFilter') {
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('stateFilter can only be used on arrays');
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 === '$stateSort') {
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 === '$stateList') {
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 StateListWrapper = () => {
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 <StateListWrapper />;
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({}, handler);
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