cogsbox-state 0.5.461 → 0.5.462

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
@@ -284,7 +284,7 @@ export type EndType<T, IsArrayElement = false> = {
284
284
  hideMessage?: boolean;
285
285
  }) => JSX.Element;
286
286
  lastSynced?: SyncInfo;
287
- } & (IsArrayElement extends true ? { cut: () => void } : {});
287
+ } & (IsArrayElement extends true ? { cutThis: () => void } : {});
288
288
 
289
289
  export type StateObject<T> = (T extends any[]
290
290
  ? ArrayEndType<T>
@@ -419,13 +419,7 @@ export type OptionsType<T extends unknown = unknown, TApiParams = never> = {
419
419
  onSuccess?: (data: any) => void;
420
420
  onError?: (error: any) => void;
421
421
  };
422
- middleware?: ({
423
- updateLog,
424
- update,
425
- }: {
426
- updateLog: UpdateTypeDetail[] | undefined;
427
- update: UpdateTypeDetail;
428
- }) => void;
422
+ middleware?: ({ update }: { update: UpdateTypeDetail }) => void;
429
423
 
430
424
  modifyState?: (state: T) => T;
431
425
  localStorage?: {
@@ -788,7 +782,7 @@ export function createCogsStateFromSync<
788
782
  const {
789
783
  getInitialOptions,
790
784
 
791
- setStateLog,
785
+ addStateLog,
792
786
  updateInitialStateGlobal,
793
787
  } = getGlobalStore.getState();
794
788
  const saveToLocalStorage = <T,>(
@@ -1008,8 +1002,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
1008
1002
 
1009
1003
  let noStateKey = stateKey ? false : true;
1010
1004
  const [thisKey] = useState(stateKey ?? uuidv4());
1011
- const stateLog = getGlobalStore.getState().stateLog[thisKey];
1012
- const componentUpdatesRef = useRef(new Set<string>());
1005
+
1013
1006
  const componentIdRef = useRef(componentId ?? uuidv4());
1014
1007
  const latestInitialOptionsRef = useRef<OptionsType<TStateObject> | null>(
1015
1008
  null
@@ -1254,6 +1247,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
1254
1247
  notifyComponents(thisKey);
1255
1248
  }
1256
1249
  }, [thisKey, ...(dependencies || [])]);
1250
+
1257
1251
  useLayoutEffect(() => {
1258
1252
  if (noStateKey) {
1259
1253
  setAndMergeOptions(thisKey as string, {
@@ -1323,19 +1317,16 @@ export function useCogsStateFn<TStateObject extends unknown>(
1323
1317
  }, []);
1324
1318
 
1325
1319
  const syncApiRef = useRef<SyncApi | null>(null);
1320
+
1326
1321
  const effectiveSetState = (
1327
1322
  newStateOrFunction: UpdateArg<TStateObject> | InsertParams<TStateObject>,
1328
1323
  path: string[],
1329
1324
  updateObj: UpdateOptions
1330
1325
  ) => {
1331
1326
  const fullPath = [thisKey, ...path].join('.');
1332
- if (Array.isArray(path)) {
1333
- const pathKey = `${thisKey}-${path.join('.')}`;
1334
- componentUpdatesRef.current.add(pathKey);
1335
- }
1327
+
1336
1328
  const store = getGlobalStore.getState();
1337
1329
 
1338
- // FETCH ONCE at the beginning
1339
1330
  const shadowMeta = store.getShadowMetadata(thisKey, path);
1340
1331
  const nestedShadowValue = store.getShadowValue(fullPath) as TStateObject;
1341
1332
 
@@ -1563,15 +1554,11 @@ export function useCogsStateFn<TStateObject extends unknown>(
1563
1554
  }
1564
1555
  }
1565
1556
 
1566
- // Assumes `isDeepEqual` is available in this scope.
1567
- // Assumes `isDeepEqual` is available in this scope.
1568
-
1569
- const newState = getGlobalStore.getState().getShadowValue(thisKey);
1570
- const rootMeta = getGlobalStore.getState().getShadowMetadata(thisKey, []);
1557
+ const rootMeta = store.getShadowMetadata(thisKey, []);
1571
1558
  const notifiedComponents = new Set<string>();
1572
1559
 
1573
1560
  if (!rootMeta?.components) {
1574
- return newState;
1561
+ return;
1575
1562
  }
1576
1563
 
1577
1564
  // --- PASS 1: Notify specific subscribers based on update type ---
@@ -1753,26 +1740,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
1753
1740
  }
1754
1741
  });
1755
1742
  notifiedComponents.clear();
1756
- setStateLog(thisKey, (prevLogs) => {
1757
- const logs = [...(prevLogs ?? []), newUpdate];
1758
- const aggregatedLogs = new Map<string, typeof newUpdate>();
1759
-
1760
- logs.forEach((log) => {
1761
- const uniqueKey = `${log.stateKey}:${JSON.stringify(log.path)}`;
1762
- const existing = aggregatedLogs.get(uniqueKey);
1763
-
1764
- if (existing) {
1765
- existing.timeStamp = Math.max(existing.timeStamp, log.timeStamp);
1766
- existing.newValue = log.newValue;
1767
- existing.oldValue = existing.oldValue ?? log.oldValue;
1768
- existing.updateType = log.updateType;
1769
- } else {
1770
- aggregatedLogs.set(uniqueKey, { ...(log as any) });
1771
- }
1772
- });
1773
-
1774
- return Array.from(aggregatedLogs.values());
1775
- });
1743
+ addStateLog(thisKey, newUpdate);
1776
1744
 
1777
1745
  saveToLocalStorage(
1778
1746
  payload,
@@ -1783,12 +1751,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
1783
1751
 
1784
1752
  if (latestInitialOptionsRef.current?.middleware) {
1785
1753
  latestInitialOptionsRef.current!.middleware({
1786
- updateLog: stateLog,
1787
1754
  update: newUpdate,
1788
1755
  });
1789
1756
  }
1790
-
1791
- return newState;
1792
1757
  };
1793
1758
 
1794
1759
  if (!getGlobalStore.getState().initialStateGlobal[thisKey]) {
@@ -1961,48 +1926,33 @@ const notifySelectionComponents = (
1961
1926
  }
1962
1927
  }
1963
1928
  };
1929
+
1964
1930
  function createProxyHandler<T>(
1965
1931
  stateKey: string,
1966
1932
  effectiveSetState: EffectiveSetState<T>,
1967
1933
  componentId: string,
1968
1934
  sessionId?: string
1969
1935
  ): StateObject<T> {
1970
- type CacheEntry = {
1971
- proxy: any;
1972
- stateVersion: number;
1973
- };
1974
-
1975
- const shapeCache = new Map<string, CacheEntry>();
1936
+ const proxyCache = new Map<string, any>();
1976
1937
  let stateVersion = 0;
1977
1938
 
1978
- const invalidateCachePath = (path: string[]) => {
1979
- const pathKey = path.join('.');
1980
- for (const [key] of shapeCache) {
1981
- if (key === pathKey || key.startsWith(pathKey + '.')) {
1982
- shapeCache.delete(key);
1983
- }
1984
- }
1985
- stateVersion++;
1986
- };
1987
-
1988
1939
  function rebuildStateShape({
1989
- currentState,
1990
1940
  path = [],
1991
1941
  meta,
1992
1942
  componentId,
1993
1943
  }: {
1994
- currentState: T;
1995
1944
  path: string[];
1996
1945
  componentId: string;
1997
1946
  meta?: MetaData;
1998
1947
  }): any {
1999
- const cacheKey = path.map(String).join('.');
1948
+ const derivationSignature = meta
1949
+ ? JSON.stringify(meta.validIds || meta.transforms)
1950
+ : '';
1951
+ const cacheKey = path.join('.') + ':' + derivationSignature;
2000
1952
  const stateKeyPathKey = [stateKey, ...path].join('.');
2001
-
2002
- currentState = getGlobalStore
2003
- .getState()
2004
- .getShadowValue(stateKeyPathKey, meta?.validIds);
2005
-
1953
+ if (proxyCache.has(cacheKey)) {
1954
+ return proxyCache.get(cacheKey);
1955
+ }
2006
1956
  type CallableStateObject<T> = {
2007
1957
  (): T;
2008
1958
  } & {
@@ -2200,1167 +2150,1154 @@ function createProxyHandler<T>(
2200
2150
  return [];
2201
2151
  };
2202
2152
  }
2203
- if (Array.isArray(currentState)) {
2204
- if (prop === 'getSelected') {
2205
- return () => {
2206
- const fullKey = stateKey + '.' + path.join('.');
2207
- registerComponentDependency(stateKey, componentId, [
2208
- ...path,
2209
- 'getSelected',
2210
- ]);
2211
-
2212
- const selectedIndicesMap =
2213
- getGlobalStore.getState().selectedIndicesMap;
2214
- if (!selectedIndicesMap || !selectedIndicesMap.has(fullKey)) {
2215
- return undefined;
2216
- }
2217
-
2218
- const selectedItemKey = selectedIndicesMap.get(fullKey)!;
2219
- if (meta?.validIds) {
2220
- if (!meta.validIds.includes(selectedItemKey)) {
2221
- return undefined;
2222
- }
2223
- }
2224
2153
 
2225
- const value = getGlobalStore
2226
- .getState()
2227
- .getShadowValue(selectedItemKey);
2154
+ if (prop === 'getSelected') {
2155
+ return () => {
2156
+ const fullKey = stateKey + '.' + path.join('.');
2157
+ registerComponentDependency(stateKey, componentId, [
2158
+ ...path,
2159
+ 'getSelected',
2160
+ ]);
2161
+
2162
+ const selectedIndicesMap =
2163
+ getGlobalStore.getState().selectedIndicesMap;
2164
+ if (!selectedIndicesMap || !selectedIndicesMap.has(fullKey)) {
2165
+ return undefined;
2166
+ }
2228
2167
 
2229
- if (!value) {
2168
+ const selectedItemKey = selectedIndicesMap.get(fullKey)!;
2169
+ if (meta?.validIds) {
2170
+ if (!meta.validIds.includes(selectedItemKey)) {
2230
2171
  return undefined;
2231
2172
  }
2173
+ }
2232
2174
 
2233
- return rebuildStateShape({
2234
- currentState: value,
2235
- path: selectedItemKey.split('.').slice(1) as string[],
2236
- componentId: componentId!,
2237
- });
2238
- };
2239
- }
2240
- if (prop === 'getSelectedIndex') {
2241
- return () => {
2242
- const selectedIndex = getGlobalStore
2243
- .getState()
2244
- .getSelectedIndex(
2245
- stateKey + '.' + path.join('.'),
2246
- meta?.validIds
2247
- );
2248
-
2249
- return selectedIndex;
2250
- };
2251
- }
2252
- if (prop === 'clearSelected') {
2253
- notifySelectionComponents(stateKey, path);
2254
- return () => {
2255
- getGlobalStore.getState().clearSelectedIndex({
2256
- arrayKey: stateKey + '.' + path.join('.'),
2257
- });
2258
- };
2259
- }
2175
+ const value = getGlobalStore
2176
+ .getState()
2177
+ .getShadowValue(selectedItemKey);
2260
2178
 
2261
- if (prop === 'useVirtualView') {
2262
- return (
2263
- options: VirtualViewOptions
2264
- ): VirtualStateObjectResult<any[]> => {
2265
- const {
2266
- itemHeight = 50,
2267
- overscan = 6,
2268
- stickToBottom = false,
2269
- scrollStickTolerance = 75,
2270
- } = options;
2271
-
2272
- const containerRef = useRef<HTMLDivElement | null>(null);
2273
- const [range, setRange] = useState({
2274
- startIndex: 0,
2275
- endIndex: 10,
2276
- });
2277
- const [rerender, forceUpdate] = useState({});
2278
- const initialScrollRef = useRef(true);
2279
-
2280
- // Scroll state management
2281
- const scrollStateRef = useRef({
2282
- isUserScrolling: false,
2283
- lastScrollTop: 0,
2284
- scrollUpCount: 0,
2285
- isNearBottom: true,
2286
- });
2179
+ if (!value) {
2180
+ return undefined;
2181
+ }
2287
2182
 
2288
- // Measurement cache
2289
- const measurementCache = useRef(
2290
- new Map<string, { height: number; offset: number }>()
2183
+ return rebuildStateShape({
2184
+ path: selectedItemKey.split('.').slice(1) as string[],
2185
+ componentId: componentId!,
2186
+ });
2187
+ };
2188
+ }
2189
+ if (prop === 'getSelectedIndex') {
2190
+ return () => {
2191
+ const selectedIndex = getGlobalStore
2192
+ .getState()
2193
+ .getSelectedIndex(
2194
+ stateKey + '.' + path.join('.'),
2195
+ meta?.validIds
2291
2196
  );
2292
2197
 
2293
- // Separate effect for handling rerender updates
2294
- useLayoutEffect(() => {
2295
- if (
2296
- !stickToBottom ||
2297
- !containerRef.current ||
2298
- scrollStateRef.current.isUserScrolling
2299
- )
2300
- return;
2198
+ return selectedIndex;
2199
+ };
2200
+ }
2201
+ if (prop === 'clearSelected') {
2202
+ notifySelectionComponents(stateKey, path);
2203
+ return () => {
2204
+ getGlobalStore.getState().clearSelectedIndex({
2205
+ arrayKey: stateKey + '.' + path.join('.'),
2206
+ });
2207
+ };
2208
+ }
2301
2209
 
2302
- const container = containerRef.current;
2303
- container.scrollTo({
2304
- top: container.scrollHeight,
2305
- behavior: initialScrollRef.current ? 'instant' : 'smooth',
2306
- });
2307
- }, [rerender, stickToBottom]);
2210
+ if (prop === 'useVirtualView') {
2211
+ return (
2212
+ options: VirtualViewOptions
2213
+ ): VirtualStateObjectResult<any[]> => {
2214
+ const {
2215
+ itemHeight = 50,
2216
+ overscan = 6,
2217
+ stickToBottom = false,
2218
+ scrollStickTolerance = 75,
2219
+ } = options;
2220
+
2221
+ const containerRef = useRef<HTMLDivElement | null>(null);
2222
+ const [range, setRange] = useState({
2223
+ startIndex: 0,
2224
+ endIndex: 10,
2225
+ });
2226
+ const [rerender, forceUpdate] = useState({});
2227
+ const initialScrollRef = useRef(true);
2228
+
2229
+ // Scroll state management
2230
+ const scrollStateRef = useRef({
2231
+ isUserScrolling: false,
2232
+ lastScrollTop: 0,
2233
+ scrollUpCount: 0,
2234
+ isNearBottom: true,
2235
+ });
2308
2236
 
2309
- const arrayKeys =
2237
+ // Measurement cache
2238
+ const measurementCache = useRef(
2239
+ new Map<string, { height: number; offset: number }>()
2240
+ );
2241
+
2242
+ // Separate effect for handling rerender updates
2243
+ useLayoutEffect(() => {
2244
+ if (
2245
+ !stickToBottom ||
2246
+ !containerRef.current ||
2247
+ scrollStateRef.current.isUserScrolling
2248
+ )
2249
+ return;
2250
+
2251
+ const container = containerRef.current;
2252
+ container.scrollTo({
2253
+ top: container.scrollHeight,
2254
+ behavior: initialScrollRef.current ? 'instant' : 'smooth',
2255
+ });
2256
+ }, [rerender, stickToBottom]);
2257
+
2258
+ const arrayKeys =
2259
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
2260
+ ?.arrayKeys || [];
2261
+
2262
+ // Calculate total height and offsets
2263
+ const { totalHeight, itemOffsets } = useMemo(() => {
2264
+ let runningOffset = 0;
2265
+ const offsets = new Map<
2266
+ string,
2267
+ { height: number; offset: number }
2268
+ >();
2269
+ const allItemKeys =
2310
2270
  getGlobalStore.getState().getShadowMetadata(stateKey, path)
2311
2271
  ?.arrayKeys || [];
2312
2272
 
2313
- // Calculate total height and offsets
2314
- const { totalHeight, itemOffsets } = useMemo(() => {
2315
- let runningOffset = 0;
2316
- const offsets = new Map<
2317
- string,
2318
- { height: number; offset: number }
2319
- >();
2320
- const allItemKeys =
2321
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2322
- ?.arrayKeys || [];
2323
-
2324
- allItemKeys.forEach((itemKey) => {
2325
- const itemPath = itemKey.split('.').slice(1);
2326
- const measuredHeight =
2327
- getGlobalStore
2328
- .getState()
2329
- .getShadowMetadata(stateKey, itemPath)?.virtualizer
2330
- ?.itemHeight || itemHeight;
2331
-
2332
- offsets.set(itemKey, {
2333
- height: measuredHeight,
2334
- offset: runningOffset,
2335
- });
2273
+ allItemKeys.forEach((itemKey) => {
2274
+ const itemPath = itemKey.split('.').slice(1);
2275
+ const measuredHeight =
2276
+ getGlobalStore
2277
+ .getState()
2278
+ .getShadowMetadata(stateKey, itemPath)?.virtualizer
2279
+ ?.itemHeight || itemHeight;
2336
2280
 
2337
- runningOffset += measuredHeight;
2281
+ offsets.set(itemKey, {
2282
+ height: measuredHeight,
2283
+ offset: runningOffset,
2338
2284
  });
2339
2285
 
2340
- measurementCache.current = offsets;
2341
- return { totalHeight: runningOffset, itemOffsets: offsets };
2342
- }, [arrayKeys.length, itemHeight]);
2343
-
2344
- // Improved initial positioning effect
2345
- useLayoutEffect(() => {
2346
- if (
2347
- stickToBottom &&
2348
- arrayKeys.length > 0 &&
2349
- containerRef.current &&
2350
- !scrollStateRef.current.isUserScrolling &&
2351
- initialScrollRef.current
2352
- ) {
2353
- const container = containerRef.current;
2354
-
2355
- // Wait for container to have dimensions
2356
- const waitForContainer = () => {
2357
- if (container.clientHeight > 0) {
2358
- const visibleCount = Math.ceil(
2359
- container.clientHeight / itemHeight
2360
- );
2361
- const endIndex = arrayKeys.length - 1;
2362
- const startIndex = Math.max(
2363
- 0,
2364
- endIndex - visibleCount - overscan
2365
- );
2286
+ runningOffset += measuredHeight;
2287
+ });
2366
2288
 
2367
- setRange({ startIndex, endIndex });
2289
+ measurementCache.current = offsets;
2290
+ return { totalHeight: runningOffset, itemOffsets: offsets };
2291
+ }, [arrayKeys.length, itemHeight]);
2368
2292
 
2369
- // Ensure scroll after range is set
2370
- requestAnimationFrame(() => {
2371
- scrollToBottom('instant');
2372
- initialScrollRef.current = false; // Mark initial scroll as done
2373
- });
2374
- } else {
2375
- // Container not ready, try again
2376
- requestAnimationFrame(waitForContainer);
2377
- }
2378
- };
2293
+ // Improved initial positioning effect
2294
+ useLayoutEffect(() => {
2295
+ if (
2296
+ stickToBottom &&
2297
+ arrayKeys.length > 0 &&
2298
+ containerRef.current &&
2299
+ !scrollStateRef.current.isUserScrolling &&
2300
+ initialScrollRef.current
2301
+ ) {
2302
+ const container = containerRef.current;
2379
2303
 
2380
- waitForContainer();
2381
- }
2382
- }, [arrayKeys.length, stickToBottom, itemHeight, overscan]);
2304
+ // Wait for container to have dimensions
2305
+ const waitForContainer = () => {
2306
+ if (container.clientHeight > 0) {
2307
+ const visibleCount = Math.ceil(
2308
+ container.clientHeight / itemHeight
2309
+ );
2310
+ const endIndex = arrayKeys.length - 1;
2311
+ const startIndex = Math.max(
2312
+ 0,
2313
+ endIndex - visibleCount - overscan
2314
+ );
2383
2315
 
2384
- // Combined scroll handler
2385
- const handleScroll = useCallback(() => {
2386
- const container = containerRef.current;
2387
- if (!container) return;
2316
+ setRange({ startIndex, endIndex });
2388
2317
 
2389
- const currentScrollTop = container.scrollTop;
2390
- const { scrollHeight, clientHeight } = container;
2391
- const scrollState = scrollStateRef.current;
2392
-
2393
- // Check if user is near bottom
2394
- const distanceFromBottom =
2395
- scrollHeight - (currentScrollTop + clientHeight);
2396
- const wasNearBottom = scrollState.isNearBottom;
2397
- scrollState.isNearBottom =
2398
- distanceFromBottom <= scrollStickTolerance;
2399
-
2400
- // Detect scroll direction
2401
- if (currentScrollTop < scrollState.lastScrollTop) {
2402
- // User scrolled up
2403
- scrollState.scrollUpCount++;
2404
-
2405
- if (scrollState.scrollUpCount > 3 && wasNearBottom) {
2406
- // User has deliberately scrolled away from bottom
2407
- scrollState.isUserScrolling = true;
2408
- console.log('User scrolled away from bottom');
2318
+ // Ensure scroll after range is set
2319
+ requestAnimationFrame(() => {
2320
+ scrollToBottom('instant');
2321
+ initialScrollRef.current = false; // Mark initial scroll as done
2322
+ });
2323
+ } else {
2324
+ // Container not ready, try again
2325
+ requestAnimationFrame(waitForContainer);
2409
2326
  }
2410
- } else if (scrollState.isNearBottom) {
2411
- // Reset if we're back near the bottom
2412
- scrollState.isUserScrolling = false;
2413
- scrollState.scrollUpCount = 0;
2327
+ };
2328
+
2329
+ waitForContainer();
2330
+ }
2331
+ }, [arrayKeys.length, stickToBottom, itemHeight, overscan]);
2332
+
2333
+ // Combined scroll handler
2334
+ const handleScroll = useCallback(() => {
2335
+ const container = containerRef.current;
2336
+ if (!container) return;
2337
+
2338
+ const currentScrollTop = container.scrollTop;
2339
+ const { scrollHeight, clientHeight } = container;
2340
+ const scrollState = scrollStateRef.current;
2341
+
2342
+ // Check if user is near bottom
2343
+ const distanceFromBottom =
2344
+ scrollHeight - (currentScrollTop + clientHeight);
2345
+ const wasNearBottom = scrollState.isNearBottom;
2346
+ scrollState.isNearBottom =
2347
+ distanceFromBottom <= scrollStickTolerance;
2348
+
2349
+ // Detect scroll direction
2350
+ if (currentScrollTop < scrollState.lastScrollTop) {
2351
+ // User scrolled up
2352
+ scrollState.scrollUpCount++;
2353
+
2354
+ if (scrollState.scrollUpCount > 3 && wasNearBottom) {
2355
+ // User has deliberately scrolled away from bottom
2356
+ scrollState.isUserScrolling = true;
2357
+ console.log('User scrolled away from bottom');
2414
2358
  }
2359
+ } else if (scrollState.isNearBottom) {
2360
+ // Reset if we're back near the bottom
2361
+ scrollState.isUserScrolling = false;
2362
+ scrollState.scrollUpCount = 0;
2363
+ }
2415
2364
 
2416
- scrollState.lastScrollTop = currentScrollTop;
2365
+ scrollState.lastScrollTop = currentScrollTop;
2417
2366
 
2418
- // Update visible range
2419
- let newStartIndex = 0;
2420
- for (let i = 0; i < arrayKeys.length; i++) {
2421
- const itemKey = arrayKeys[i];
2422
- const item = measurementCache.current.get(itemKey!);
2423
- if (item && item.offset + item.height > currentScrollTop) {
2424
- newStartIndex = i;
2425
- break;
2426
- }
2367
+ // Update visible range
2368
+ let newStartIndex = 0;
2369
+ for (let i = 0; i < arrayKeys.length; i++) {
2370
+ const itemKey = arrayKeys[i];
2371
+ const item = measurementCache.current.get(itemKey!);
2372
+ if (item && item.offset + item.height > currentScrollTop) {
2373
+ newStartIndex = i;
2374
+ break;
2427
2375
  }
2376
+ }
2428
2377
 
2429
- // Only update if range actually changed
2430
- if (newStartIndex !== range.startIndex) {
2431
- const visibleCount = Math.ceil(clientHeight / itemHeight);
2432
- setRange({
2433
- startIndex: Math.max(0, newStartIndex - overscan),
2434
- endIndex: Math.min(
2435
- arrayKeys.length - 1,
2436
- newStartIndex + visibleCount + overscan
2437
- ),
2438
- });
2439
- }
2440
- }, [
2441
- arrayKeys.length,
2442
- range.startIndex,
2443
- itemHeight,
2444
- overscan,
2445
- scrollStickTolerance,
2446
- ]);
2378
+ // Only update if range actually changed
2379
+ if (newStartIndex !== range.startIndex) {
2380
+ const visibleCount = Math.ceil(clientHeight / itemHeight);
2381
+ setRange({
2382
+ startIndex: Math.max(0, newStartIndex - overscan),
2383
+ endIndex: Math.min(
2384
+ arrayKeys.length - 1,
2385
+ newStartIndex + visibleCount + overscan
2386
+ ),
2387
+ });
2388
+ }
2389
+ }, [
2390
+ arrayKeys.length,
2391
+ range.startIndex,
2392
+ itemHeight,
2393
+ overscan,
2394
+ scrollStickTolerance,
2395
+ ]);
2396
+
2397
+ // Set up scroll listener
2398
+ useEffect(() => {
2399
+ const container = containerRef.current;
2400
+ if (!container || !stickToBottom) return;
2401
+
2402
+ container.addEventListener('scroll', handleScroll, {
2403
+ passive: true,
2404
+ });
2447
2405
 
2448
- // Set up scroll listener
2449
- useEffect(() => {
2406
+ return () => {
2407
+ container.removeEventListener('scroll', handleScroll);
2408
+ };
2409
+ }, [handleScroll, stickToBottom]);
2410
+ const scrollToBottom = useCallback(
2411
+ (behavior: ScrollBehavior = 'smooth') => {
2450
2412
  const container = containerRef.current;
2451
- if (!container || !stickToBottom) return;
2413
+ if (!container) return;
2452
2414
 
2453
- container.addEventListener('scroll', handleScroll, {
2454
- passive: true,
2455
- });
2415
+ // Reset scroll state
2416
+ scrollStateRef.current.isUserScrolling = false;
2417
+ scrollStateRef.current.isNearBottom = true;
2418
+ scrollStateRef.current.scrollUpCount = 0;
2456
2419
 
2457
- return () => {
2458
- container.removeEventListener('scroll', handleScroll);
2459
- };
2460
- }, [handleScroll, stickToBottom]);
2461
- const scrollToBottom = useCallback(
2462
- (behavior: ScrollBehavior = 'smooth') => {
2463
- const container = containerRef.current;
2464
- if (!container) return;
2465
-
2466
- // Reset scroll state
2467
- scrollStateRef.current.isUserScrolling = false;
2468
- scrollStateRef.current.isNearBottom = true;
2469
- scrollStateRef.current.scrollUpCount = 0;
2470
-
2471
- const performScroll = () => {
2472
- // Multiple attempts to ensure we hit the bottom
2473
- const attemptScroll = (attempts = 0) => {
2474
- if (attempts > 5) return; // Prevent infinite loops
2475
-
2476
- const currentHeight = container.scrollHeight;
2477
- const currentScroll = container.scrollTop;
2478
- const clientHeight = container.clientHeight;
2479
-
2480
- // Check if we're already at the bottom
2481
- if (currentScroll + clientHeight >= currentHeight - 1) {
2482
- return;
2483
- }
2420
+ const performScroll = () => {
2421
+ // Multiple attempts to ensure we hit the bottom
2422
+ const attemptScroll = (attempts = 0) => {
2423
+ if (attempts > 5) return; // Prevent infinite loops
2484
2424
 
2485
- container.scrollTo({
2486
- top: currentHeight,
2487
- behavior: behavior,
2488
- });
2489
-
2490
- // In slow environments, check again after a short delay
2491
- setTimeout(() => {
2492
- const newHeight = container.scrollHeight;
2493
- const newScroll = container.scrollTop;
2494
-
2495
- // If height changed or we're not at bottom, try again
2496
- if (
2497
- newHeight !== currentHeight ||
2498
- newScroll + clientHeight < newHeight - 1
2499
- ) {
2500
- attemptScroll(attempts + 1);
2501
- }
2502
- }, 50);
2503
- };
2425
+ const currentHeight = container.scrollHeight;
2426
+ const currentScroll = container.scrollTop;
2427
+ const clientHeight = container.clientHeight;
2504
2428
 
2505
- attemptScroll();
2506
- };
2429
+ // Check if we're already at the bottom
2430
+ if (currentScroll + clientHeight >= currentHeight - 1) {
2431
+ return;
2432
+ }
2507
2433
 
2508
- // Use requestIdleCallback for better performance in slow environments
2509
- if ('requestIdleCallback' in window) {
2510
- requestIdleCallback(performScroll, { timeout: 100 });
2511
- } else {
2512
- // Fallback to rAF chain
2513
- requestAnimationFrame(() => {
2514
- requestAnimationFrame(performScroll);
2434
+ container.scrollTo({
2435
+ top: currentHeight,
2436
+ behavior: behavior,
2515
2437
  });
2516
- }
2517
- },
2518
- []
2519
- );
2520
- // Auto-scroll to bottom when new content arrives
2521
- // Consolidated auto-scroll effect with debouncing
2522
- useEffect(() => {
2523
- if (!stickToBottom || !containerRef.current) return;
2524
2438
 
2525
- const container = containerRef.current;
2526
- const scrollState = scrollStateRef.current;
2527
-
2528
- // Debounced scroll function
2529
- let scrollTimeout: NodeJS.Timeout;
2530
- const debouncedScrollToBottom = () => {
2531
- clearTimeout(scrollTimeout);
2532
- scrollTimeout = setTimeout(() => {
2533
- if (
2534
- !scrollState.isUserScrolling &&
2535
- scrollState.isNearBottom
2536
- ) {
2537
- scrollToBottom(
2538
- initialScrollRef.current ? 'instant' : 'smooth'
2539
- );
2540
- }
2541
- }, 100);
2542
- };
2439
+ // In slow environments, check again after a short delay
2440
+ setTimeout(() => {
2441
+ const newHeight = container.scrollHeight;
2442
+ const newScroll = container.scrollTop;
2543
2443
 
2544
- // Single MutationObserver for all DOM changes
2545
- const observer = new MutationObserver(() => {
2546
- if (!scrollState.isUserScrolling) {
2547
- debouncedScrollToBottom();
2548
- }
2549
- });
2444
+ // If height changed or we're not at bottom, try again
2445
+ if (
2446
+ newHeight !== currentHeight ||
2447
+ newScroll + clientHeight < newHeight - 1
2448
+ ) {
2449
+ attemptScroll(attempts + 1);
2450
+ }
2451
+ }, 50);
2452
+ };
2550
2453
 
2551
- observer.observe(container, {
2552
- childList: true,
2553
- subtree: true,
2554
- attributes: true,
2555
- attributeFilter: ['style', 'class'], // More specific than just 'height'
2556
- });
2454
+ attemptScroll();
2455
+ };
2557
2456
 
2558
- // Handle image loads with event delegation
2559
- const handleImageLoad = (e: Event) => {
2457
+ // Use requestIdleCallback for better performance in slow environments
2458
+ if ('requestIdleCallback' in window) {
2459
+ requestIdleCallback(performScroll, { timeout: 100 });
2460
+ } else {
2461
+ // Fallback to rAF chain
2462
+ requestAnimationFrame(() => {
2463
+ requestAnimationFrame(performScroll);
2464
+ });
2465
+ }
2466
+ },
2467
+ []
2468
+ );
2469
+ // Auto-scroll to bottom when new content arrives
2470
+ // Consolidated auto-scroll effect with debouncing
2471
+ useEffect(() => {
2472
+ if (!stickToBottom || !containerRef.current) return;
2473
+
2474
+ const container = containerRef.current;
2475
+ const scrollState = scrollStateRef.current;
2476
+
2477
+ // Debounced scroll function
2478
+ let scrollTimeout: NodeJS.Timeout;
2479
+ const debouncedScrollToBottom = () => {
2480
+ clearTimeout(scrollTimeout);
2481
+ scrollTimeout = setTimeout(() => {
2560
2482
  if (
2561
- e.target instanceof HTMLImageElement &&
2562
- !scrollState.isUserScrolling
2483
+ !scrollState.isUserScrolling &&
2484
+ scrollState.isNearBottom
2563
2485
  ) {
2564
- debouncedScrollToBottom();
2486
+ scrollToBottom(
2487
+ initialScrollRef.current ? 'instant' : 'smooth'
2488
+ );
2565
2489
  }
2566
- };
2490
+ }, 100);
2491
+ };
2492
+
2493
+ // Single MutationObserver for all DOM changes
2494
+ const observer = new MutationObserver(() => {
2495
+ if (!scrollState.isUserScrolling) {
2496
+ debouncedScrollToBottom();
2497
+ }
2498
+ });
2567
2499
 
2568
- container.addEventListener('load', handleImageLoad, true);
2500
+ observer.observe(container, {
2501
+ childList: true,
2502
+ subtree: true,
2503
+ attributes: true,
2504
+ attributeFilter: ['style', 'class'], // More specific than just 'height'
2505
+ });
2569
2506
 
2570
- // Initial scroll with proper timing
2571
- if (initialScrollRef.current) {
2572
- // For initial load, wait for next tick to ensure DOM is ready
2573
- setTimeout(() => {
2574
- scrollToBottom('instant');
2575
- }, 0);
2576
- } else {
2507
+ // Handle image loads with event delegation
2508
+ const handleImageLoad = (e: Event) => {
2509
+ if (
2510
+ e.target instanceof HTMLImageElement &&
2511
+ !scrollState.isUserScrolling
2512
+ ) {
2577
2513
  debouncedScrollToBottom();
2578
2514
  }
2515
+ };
2579
2516
 
2580
- return () => {
2581
- clearTimeout(scrollTimeout);
2582
- observer.disconnect();
2583
- container.removeEventListener('load', handleImageLoad, true);
2584
- };
2585
- }, [stickToBottom, arrayKeys.length, scrollToBottom]);
2586
- // Create virtual state
2587
- const virtualState = useMemo(() => {
2588
- const store = getGlobalStore.getState();
2589
- const sourceArray = store.getShadowValue(
2590
- [stateKey, ...path].join('.')
2591
- ) as any[];
2592
- const currentKeys =
2593
- store.getShadowMetadata(stateKey, path)?.arrayKeys || [];
2594
-
2595
- const slicedArray = sourceArray.slice(
2596
- range.startIndex,
2597
- range.endIndex + 1
2598
- );
2599
- const slicedIds = currentKeys.slice(
2600
- range.startIndex,
2601
- range.endIndex + 1
2602
- );
2517
+ container.addEventListener('load', handleImageLoad, true);
2603
2518
 
2604
- return rebuildStateShape({
2605
- currentState: slicedArray as any,
2606
- path,
2607
- componentId: componentId!,
2608
- meta: { ...meta, validIds: slicedIds },
2609
- });
2610
- }, [range.startIndex, range.endIndex, arrayKeys.length]);
2611
-
2612
- return {
2613
- virtualState,
2614
- virtualizerProps: {
2615
- outer: {
2616
- ref: containerRef,
2617
- style: {
2618
- overflowY: 'auto',
2619
- height: '100%',
2620
- position: 'relative',
2621
- },
2622
- },
2623
- inner: {
2624
- style: {
2625
- height: `${totalHeight}px`,
2626
- position: 'relative',
2627
- },
2519
+ // Initial scroll with proper timing
2520
+ if (initialScrollRef.current) {
2521
+ // For initial load, wait for next tick to ensure DOM is ready
2522
+ setTimeout(() => {
2523
+ scrollToBottom('instant');
2524
+ }, 0);
2525
+ } else {
2526
+ debouncedScrollToBottom();
2527
+ }
2528
+
2529
+ return () => {
2530
+ clearTimeout(scrollTimeout);
2531
+ observer.disconnect();
2532
+ container.removeEventListener('load', handleImageLoad, true);
2533
+ };
2534
+ }, [stickToBottom, arrayKeys.length, scrollToBottom]);
2535
+ // Create virtual state
2536
+ const virtualState = useMemo(() => {
2537
+ const store = getGlobalStore.getState();
2538
+ const sourceArray = store.getShadowValue(
2539
+ [stateKey, ...path].join('.')
2540
+ ) as any[];
2541
+ const currentKeys =
2542
+ store.getShadowMetadata(stateKey, path)?.arrayKeys || [];
2543
+
2544
+ const slicedArray = sourceArray.slice(
2545
+ range.startIndex,
2546
+ range.endIndex + 1
2547
+ );
2548
+ const slicedIds = currentKeys.slice(
2549
+ range.startIndex,
2550
+ range.endIndex + 1
2551
+ );
2552
+
2553
+ return rebuildStateShape({
2554
+ path,
2555
+ componentId: componentId!,
2556
+ meta: { ...meta, validIds: slicedIds },
2557
+ });
2558
+ }, [range.startIndex, range.endIndex, arrayKeys.length]);
2559
+
2560
+ return {
2561
+ virtualState,
2562
+ virtualizerProps: {
2563
+ outer: {
2564
+ ref: containerRef,
2565
+ style: {
2566
+ overflowY: 'auto',
2567
+ height: '100%',
2568
+ position: 'relative',
2628
2569
  },
2629
- list: {
2630
- style: {
2631
- transform: `translateY(${
2632
- measurementCache.current.get(
2633
- arrayKeys[range.startIndex]!
2634
- )?.offset || 0
2635
- }px)`,
2636
- },
2570
+ },
2571
+ inner: {
2572
+ style: {
2573
+ height: `${totalHeight}px`,
2574
+ position: 'relative',
2637
2575
  },
2638
2576
  },
2639
- scrollToBottom,
2640
- scrollToIndex: (
2641
- index: number,
2642
- behavior: ScrollBehavior = 'smooth'
2643
- ) => {
2644
- if (containerRef.current && arrayKeys[index]) {
2645
- const offset =
2646
- measurementCache.current.get(arrayKeys[index]!)?.offset ||
2647
- 0;
2648
- containerRef.current.scrollTo({ top: offset, behavior });
2649
- }
2577
+ list: {
2578
+ style: {
2579
+ transform: `translateY(${
2580
+ measurementCache.current.get(arrayKeys[range.startIndex]!)
2581
+ ?.offset || 0
2582
+ }px)`,
2583
+ },
2650
2584
  },
2651
- };
2652
- };
2653
- }
2654
- if (prop === 'stateMap') {
2655
- return (
2656
- callbackfn: (
2657
- setter: any,
2585
+ },
2586
+ scrollToBottom,
2587
+ scrollToIndex: (
2658
2588
  index: number,
2589
+ behavior: ScrollBehavior = 'smooth'
2590
+ ) => {
2591
+ if (containerRef.current && arrayKeys[index]) {
2592
+ const offset =
2593
+ measurementCache.current.get(arrayKeys[index]!)?.offset ||
2594
+ 0;
2595
+ containerRef.current.scrollTo({ top: offset, behavior });
2596
+ }
2597
+ },
2598
+ };
2599
+ };
2600
+ }
2601
+ if (prop === 'stateMap') {
2602
+ return (
2603
+ callbackfn: (
2604
+ setter: any,
2605
+ index: number,
2606
+
2607
+ arraySetter: any
2608
+ ) => void
2609
+ ) => {
2610
+ const [arrayKeys, setArrayKeys] = useState<any>(
2611
+ meta?.validIds ??
2612
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
2613
+ ?.arrayKeys
2614
+ );
2615
+ // getGlobalStore.getState().subscribeToPath(stateKeyPathKey, () => {
2616
+ // console.log(
2617
+ // "stateKeyPathKeyccccccccccccccccc",
2618
+ // stateKeyPathKey
2619
+ // );
2620
+ // setArrayKeys(
2621
+ // getGlobalStore.getState().getShadowMetadata(stateKey, path)
2622
+ // );
2623
+ // });
2624
+
2625
+ const shadowValue = getGlobalStore
2626
+ .getState()
2627
+ .getShadowValue(stateKeyPathKey, meta?.validIds) as any[];
2628
+ if (!arrayKeys) {
2629
+ throw new Error('No array keys found for mapping');
2630
+ }
2631
+ const arraySetter = rebuildStateShape({
2632
+ path,
2633
+ componentId: componentId!,
2634
+ meta,
2635
+ });
2659
2636
 
2660
- arraySetter: any
2661
- ) => void
2662
- ) => {
2663
- const [arrayKeys, setArrayKeys] = useState<any>(
2664
- meta?.validIds ??
2665
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2666
- ?.arrayKeys
2667
- );
2668
- // getGlobalStore.getState().subscribeToPath(stateKeyPathKey, () => {
2669
- // console.log(
2670
- // "stateKeyPathKeyccccccccccccccccc",
2671
- // stateKeyPathKey
2672
- // );
2673
- // setArrayKeys(
2674
- // getGlobalStore.getState().getShadowMetadata(stateKey, path)
2675
- // );
2676
- // });
2677
-
2678
- const shadowValue = getGlobalStore
2679
- .getState()
2680
- .getShadowValue(stateKeyPathKey, meta?.validIds) as any[];
2681
- if (!arrayKeys) {
2682
- throw new Error('No array keys found for mapping');
2683
- }
2684
- const arraySetter = rebuildStateShape({
2685
- currentState: shadowValue as any,
2686
- path,
2637
+ return shadowValue.map((item, index) => {
2638
+ const itemPath = arrayKeys[index]?.split('.').slice(1);
2639
+ const itemSetter = rebuildStateShape({
2640
+ path: itemPath as any,
2687
2641
  componentId: componentId!,
2688
2642
  meta,
2689
2643
  });
2690
2644
 
2691
- return shadowValue.map((item, index) => {
2692
- const itemPath = arrayKeys[index]?.split('.').slice(1);
2693
- const itemSetter = rebuildStateShape({
2694
- currentState: item,
2695
- path: itemPath as any,
2696
- componentId: componentId!,
2697
- meta,
2698
- });
2645
+ return callbackfn(
2646
+ itemSetter,
2647
+ index,
2699
2648
 
2700
- return callbackfn(
2701
- itemSetter,
2702
- index,
2649
+ arraySetter
2650
+ );
2651
+ });
2652
+ };
2653
+ }
2703
2654
 
2704
- arraySetter
2705
- );
2706
- });
2707
- };
2708
- }
2655
+ if (prop === '$stateMap') {
2656
+ return (callbackfn: any) =>
2657
+ createElement(SignalMapRenderer, {
2658
+ proxy: {
2659
+ _stateKey: stateKey,
2660
+ _path: path,
2661
+ _mapFn: callbackfn,
2662
+ _meta: meta,
2663
+ },
2664
+ rebuildStateShape,
2665
+ });
2666
+ } // In createProxyHandler -> handler -> get -> if (Array.isArray(currentState))
2667
+
2668
+ if (prop === 'stateFind') {
2669
+ return (
2670
+ callbackfn: (value: any, index: number) => boolean
2671
+ ): StateObject<any> | undefined => {
2672
+ // 1. Use the correct set of keys: filtered/sorted from meta, or all keys from the store.
2673
+ const arrayKeys =
2674
+ meta?.validIds ??
2675
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
2676
+ ?.arrayKeys;
2677
+
2678
+ if (!arrayKeys) {
2679
+ return undefined;
2680
+ }
2709
2681
 
2710
- if (prop === '$stateMap') {
2711
- return (callbackfn: any) =>
2712
- createElement(SignalMapRenderer, {
2713
- proxy: {
2714
- _stateKey: stateKey,
2715
- _path: path,
2716
- _mapFn: callbackfn,
2717
- _meta: meta,
2718
- },
2719
- rebuildStateShape,
2720
- });
2721
- } // In createProxyHandler -> handler -> get -> if (Array.isArray(currentState))
2682
+ // 2. Iterate through the keys, get the value for each, and run the callback.
2683
+ for (let i = 0; i < arrayKeys.length; i++) {
2684
+ const itemKey = arrayKeys[i];
2685
+ if (!itemKey) continue; // Safety check
2722
2686
 
2723
- if (prop === 'stateFind') {
2724
- return (
2725
- callbackfn: (value: any, index: number) => boolean
2726
- ): StateObject<any> | undefined => {
2727
- // 1. Use the correct set of keys: filtered/sorted from meta, or all keys from the store.
2728
- const arrayKeys =
2729
- meta?.validIds ??
2730
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2731
- ?.arrayKeys;
2687
+ const itemValue = getGlobalStore
2688
+ .getState()
2689
+ .getShadowValue(itemKey);
2732
2690
 
2733
- if (!arrayKeys) {
2734
- return undefined;
2691
+ // 3. If the callback returns true, we've found our item.
2692
+ if (callbackfn(itemValue, i)) {
2693
+ // Get the item's path relative to the stateKey (e.g., ['messages', '42'] -> ['42'])
2694
+ const itemPath = itemKey.split('.').slice(1);
2695
+
2696
+ // 4. Rebuild a new, fully functional StateObject for just that item and return it.
2697
+ return rebuildStateShape({
2698
+ path: itemPath,
2699
+ componentId: componentId,
2700
+ meta, // Pass along meta for potential further chaining
2701
+ });
2735
2702
  }
2703
+ }
2736
2704
 
2737
- // 2. Iterate through the keys, get the value for each, and run the callback.
2738
- for (let i = 0; i < arrayKeys.length; i++) {
2739
- const itemKey = arrayKeys[i];
2740
- if (!itemKey) continue; // Safety check
2705
+ // 5. If the loop finishes without finding anything, return undefined.
2706
+ return undefined;
2707
+ };
2708
+ }
2709
+ if (prop === 'stateFilter') {
2710
+ return (callbackfn: (value: any, index: number) => boolean) => {
2711
+ const currentState = getGlobalStore
2712
+ .getState()
2713
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
2714
+ if (!Array.isArray(currentState)) return [];
2715
+ const arrayKeys =
2716
+ meta?.validIds ??
2717
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
2718
+ ?.arrayKeys;
2719
+
2720
+ if (!arrayKeys) {
2721
+ throw new Error('No array keys found for filtering.');
2722
+ }
2741
2723
 
2742
- const itemValue = getGlobalStore
2743
- .getState()
2744
- .getShadowValue(itemKey);
2745
-
2746
- // 3. If the callback returns true, we've found our item.
2747
- if (callbackfn(itemValue, i)) {
2748
- // Get the item's path relative to the stateKey (e.g., ['messages', '42'] -> ['42'])
2749
- const itemPath = itemKey.split('.').slice(1);
2750
-
2751
- // 4. Rebuild a new, fully functional StateObject for just that item and return it.
2752
- return rebuildStateShape({
2753
- currentState: itemValue,
2754
- path: itemPath,
2755
- componentId: componentId,
2756
- meta, // Pass along meta for potential further chaining
2757
- });
2724
+ const newValidIds: string[] = [];
2725
+ const filteredArray = currentState.filter(
2726
+ (val: any, index: number) => {
2727
+ const didPass = callbackfn(val, index);
2728
+ if (didPass) {
2729
+ newValidIds.push(arrayKeys[index]!);
2730
+ return true;
2758
2731
  }
2732
+ return false;
2759
2733
  }
2734
+ );
2760
2735
 
2761
- // 5. If the loop finishes without finding anything, return undefined.
2762
- return undefined;
2763
- };
2764
- }
2765
- if (prop === 'stateFilter') {
2766
- return (callbackfn: (value: any, index: number) => boolean) => {
2767
- const arrayKeys =
2768
- meta?.validIds ??
2769
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2770
- ?.arrayKeys;
2771
-
2772
- if (!arrayKeys) {
2773
- throw new Error('No array keys found for filtering.');
2736
+ return rebuildStateShape({
2737
+ path,
2738
+ componentId: componentId!,
2739
+ meta: {
2740
+ validIds: newValidIds,
2741
+ transforms: [
2742
+ ...(meta?.transforms || []),
2743
+ {
2744
+ type: 'filter',
2745
+ fn: callbackfn,
2746
+ },
2747
+ ],
2748
+ },
2749
+ });
2750
+ };
2751
+ }
2752
+ if (prop === 'stateSort') {
2753
+ return (compareFn: (a: any, b: any) => number) => {
2754
+ const currentState = getGlobalStore
2755
+ .getState()
2756
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
2757
+ if (!Array.isArray(currentState)) return []; // Guard clause
2758
+ const arrayKeys =
2759
+ meta?.validIds ??
2760
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
2761
+ ?.arrayKeys;
2762
+ if (!arrayKeys) {
2763
+ throw new Error('No array keys found for sorting');
2764
+ }
2765
+ const itemsWithIds = currentState.map((item, index) => ({
2766
+ item,
2767
+ key: arrayKeys[index],
2768
+ }));
2769
+
2770
+ itemsWithIds
2771
+ .sort((a, b) => compareFn(a.item, b.item))
2772
+ .filter(Boolean);
2773
+
2774
+ return rebuildStateShape({
2775
+ path,
2776
+ componentId: componentId!,
2777
+ meta: {
2778
+ validIds: itemsWithIds.map((i) => i.key) as string[],
2779
+ transforms: [
2780
+ ...(meta?.transforms || []),
2781
+ { type: 'sort', fn: compareFn },
2782
+ ],
2783
+ },
2784
+ });
2785
+ };
2786
+ }
2787
+ // In createProxyHandler, inside the get trap where you have other array methods:
2788
+ if (prop === 'stream') {
2789
+ return function <U = InferArrayElement<T>, R = U>(
2790
+ options: StreamOptions<U, R> = {}
2791
+ ): StreamHandle<U> {
2792
+ const {
2793
+ bufferSize = 100,
2794
+ flushInterval = 100,
2795
+ bufferStrategy = 'accumulate',
2796
+ store,
2797
+ onFlush,
2798
+ } = options;
2799
+
2800
+ let buffer: U[] = [];
2801
+ let isPaused = false;
2802
+ let flushTimer: NodeJS.Timeout | null = null;
2803
+
2804
+ const addToBuffer = (item: U) => {
2805
+ if (isPaused) return;
2806
+
2807
+ if (bufferStrategy === 'sliding' && buffer.length >= bufferSize) {
2808
+ buffer.shift();
2809
+ } else if (
2810
+ bufferStrategy === 'dropping' &&
2811
+ buffer.length >= bufferSize
2812
+ ) {
2813
+ return;
2774
2814
  }
2775
2815
 
2776
- const newValidIds: string[] = [];
2777
- const filteredArray = currentState.filter(
2778
- (val: any, index: number) => {
2779
- const didPass = callbackfn(val, index);
2780
- if (didPass) {
2781
- newValidIds.push(arrayKeys[index]!);
2782
- return true;
2783
- }
2784
- return false;
2785
- }
2786
- );
2816
+ buffer.push(item);
2787
2817
 
2788
- return rebuildStateShape({
2789
- currentState: filteredArray as any,
2790
- path,
2791
- componentId: componentId!,
2792
- meta: {
2793
- validIds: newValidIds,
2794
- transforms: [
2795
- ...(meta?.transforms || []),
2796
- {
2797
- type: 'filter',
2798
- fn: callbackfn,
2799
- },
2800
- ],
2801
- },
2802
- });
2803
- };
2804
- }
2805
- if (prop === 'stateSort') {
2806
- return (compareFn: (a: any, b: any) => number) => {
2807
- const arrayKeys =
2808
- meta?.validIds ??
2809
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
2810
- ?.arrayKeys;
2811
- if (!arrayKeys) {
2812
- throw new Error('No array keys found for sorting');
2818
+ if (buffer.length >= bufferSize) {
2819
+ flushBuffer();
2813
2820
  }
2814
- const itemsWithIds = currentState.map((item, index) => ({
2815
- item,
2816
- key: arrayKeys[index],
2817
- }));
2818
-
2819
- itemsWithIds
2820
- .sort((a, b) => compareFn(a.item, b.item))
2821
- .filter(Boolean);
2822
-
2823
- return rebuildStateShape({
2824
- currentState: itemsWithIds.map((i) => i.item) as any,
2825
- path,
2826
- componentId: componentId!,
2827
- meta: {
2828
- validIds: itemsWithIds.map((i) => i.key) as string[],
2829
- transforms: [
2830
- ...(meta?.transforms || []),
2831
- { type: 'sort', fn: compareFn },
2832
- ],
2833
- },
2834
- });
2835
2821
  };
2836
- }
2837
- // In createProxyHandler, inside the get trap where you have other array methods:
2838
- if (prop === 'stream') {
2839
- return function <U = InferArrayElement<T>, R = U>(
2840
- options: StreamOptions<U, R> = {}
2841
- ): StreamHandle<U> {
2842
- const {
2843
- bufferSize = 100,
2844
- flushInterval = 100,
2845
- bufferStrategy = 'accumulate',
2846
- store,
2847
- onFlush,
2848
- } = options;
2849
-
2850
- let buffer: U[] = [];
2851
- let isPaused = false;
2852
- let flushTimer: NodeJS.Timeout | null = null;
2853
-
2854
- const addToBuffer = (item: U) => {
2855
- if (isPaused) return;
2856
2822
 
2857
- if (
2858
- bufferStrategy === 'sliding' &&
2859
- buffer.length >= bufferSize
2860
- ) {
2861
- buffer.shift();
2862
- } else if (
2863
- bufferStrategy === 'dropping' &&
2864
- buffer.length >= bufferSize
2865
- ) {
2866
- return;
2867
- }
2868
-
2869
- buffer.push(item);
2870
-
2871
- if (buffer.length >= bufferSize) {
2872
- flushBuffer();
2873
- }
2874
- };
2875
-
2876
- const flushBuffer = () => {
2877
- if (buffer.length === 0) return;
2823
+ const flushBuffer = () => {
2824
+ if (buffer.length === 0) return;
2878
2825
 
2879
- const toFlush = [...buffer];
2880
- buffer = [];
2826
+ const toFlush = [...buffer];
2827
+ buffer = [];
2881
2828
 
2882
- if (store) {
2883
- const result = store(toFlush);
2884
- if (result !== undefined) {
2885
- const items = Array.isArray(result) ? result : [result];
2886
- items.forEach((item) => {
2887
- effectiveSetState(item as any, path, {
2888
- updateType: 'insert',
2889
- });
2890
- });
2891
- }
2892
- } else {
2893
- toFlush.forEach((item) => {
2829
+ if (store) {
2830
+ const result = store(toFlush);
2831
+ if (result !== undefined) {
2832
+ const items = Array.isArray(result) ? result : [result];
2833
+ items.forEach((item) => {
2894
2834
  effectiveSetState(item as any, path, {
2895
2835
  updateType: 'insert',
2896
2836
  });
2897
2837
  });
2898
2838
  }
2839
+ } else {
2840
+ toFlush.forEach((item) => {
2841
+ effectiveSetState(item as any, path, {
2842
+ updateType: 'insert',
2843
+ });
2844
+ });
2845
+ }
2899
2846
 
2900
- onFlush?.(toFlush);
2901
- };
2847
+ onFlush?.(toFlush);
2848
+ };
2902
2849
 
2903
- if (flushInterval > 0) {
2904
- flushTimer = setInterval(flushBuffer, flushInterval);
2905
- }
2850
+ if (flushInterval > 0) {
2851
+ flushTimer = setInterval(flushBuffer, flushInterval);
2852
+ }
2906
2853
 
2907
- const streamId = uuidv4();
2908
- const currentMeta =
2909
- getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
2910
- {};
2911
- const streams = currentMeta.streams || new Map();
2912
- streams.set(streamId, { buffer, flushTimer });
2854
+ const streamId = uuidv4();
2855
+ const currentMeta =
2856
+ getGlobalStore.getState().getShadowMetadata(stateKey, path) || {};
2857
+ const streams = currentMeta.streams || new Map();
2858
+ streams.set(streamId, { buffer, flushTimer });
2913
2859
 
2914
- getGlobalStore.getState().setShadowMetadata(stateKey, path, {
2915
- ...currentMeta,
2916
- streams,
2917
- });
2860
+ getGlobalStore.getState().setShadowMetadata(stateKey, path, {
2861
+ ...currentMeta,
2862
+ streams,
2863
+ });
2918
2864
 
2919
- return {
2920
- write: (data: U) => addToBuffer(data),
2921
- writeMany: (data: U[]) => data.forEach(addToBuffer),
2922
- flush: () => flushBuffer(),
2923
- pause: () => {
2924
- isPaused = true;
2925
- },
2926
- resume: () => {
2927
- isPaused = false;
2928
- if (buffer.length > 0) flushBuffer();
2929
- },
2930
- close: () => {
2931
- flushBuffer();
2932
- if (flushTimer) clearInterval(flushTimer);
2865
+ return {
2866
+ write: (data: U) => addToBuffer(data),
2867
+ writeMany: (data: U[]) => data.forEach(addToBuffer),
2868
+ flush: () => flushBuffer(),
2869
+ pause: () => {
2870
+ isPaused = true;
2871
+ },
2872
+ resume: () => {
2873
+ isPaused = false;
2874
+ if (buffer.length > 0) flushBuffer();
2875
+ },
2876
+ close: () => {
2877
+ flushBuffer();
2878
+ if (flushTimer) clearInterval(flushTimer);
2933
2879
 
2934
- const meta = getGlobalStore
2935
- .getState()
2936
- .getShadowMetadata(stateKey, path);
2937
- if (meta?.streams) {
2938
- meta.streams.delete(streamId);
2939
- }
2940
- },
2941
- };
2880
+ const meta = getGlobalStore
2881
+ .getState()
2882
+ .getShadowMetadata(stateKey, path);
2883
+ if (meta?.streams) {
2884
+ meta.streams.delete(streamId);
2885
+ }
2886
+ },
2942
2887
  };
2943
- }
2888
+ };
2889
+ }
2944
2890
 
2945
- if (prop === 'stateList') {
2946
- return (
2947
- callbackfn: (
2948
- setter: any,
2949
- index: number,
2950
- arraySetter: any
2951
- ) => ReactNode
2952
- ) => {
2953
- const StateListWrapper = () => {
2954
- const componentIdsRef = useRef<Map<string, string>>(new Map());
2891
+ if (prop === 'stateList') {
2892
+ return (
2893
+ callbackfn: (
2894
+ setter: any,
2895
+ index: number,
2896
+ arraySetter: any
2897
+ ) => ReactNode
2898
+ ) => {
2899
+ const StateListWrapper = () => {
2900
+ const componentIdsRef = useRef<Map<string, string>>(new Map());
2901
+
2902
+ const cacheKey =
2903
+ meta?.transforms && meta.transforms.length > 0
2904
+ ? `${componentId}-${hashTransforms(meta.transforms)}`
2905
+ : `${componentId}-base`;
2906
+
2907
+ const [updateTrigger, forceUpdate] = useState({});
2908
+
2909
+ const { validIds, arrayValues } = useMemo(() => {
2910
+ const cached = getGlobalStore
2911
+ .getState()
2912
+ .getShadowMetadata(stateKey, path)
2913
+ ?.transformCaches?.get(cacheKey);
2955
2914
 
2956
- const cacheKey =
2957
- meta?.transforms && meta.transforms.length > 0
2958
- ? `${componentId}-${hashTransforms(meta.transforms)}`
2959
- : `${componentId}-base`;
2915
+ let freshValidIds: string[];
2960
2916
 
2961
- const [updateTrigger, forceUpdate] = useState({});
2917
+ if (cached && cached.validIds) {
2918
+ freshValidIds = cached.validIds;
2919
+ } else {
2920
+ freshValidIds = applyTransforms(
2921
+ stateKey,
2922
+ path,
2923
+ meta?.transforms
2924
+ );
2962
2925
 
2963
- const { validIds, arrayValues } = useMemo(() => {
2964
- const cached = getGlobalStore
2926
+ getGlobalStore
2965
2927
  .getState()
2966
- .getShadowMetadata(stateKey, path)
2967
- ?.transformCaches?.get(cacheKey);
2968
-
2969
- let freshValidIds: string[];
2970
-
2971
- if (cached && cached.validIds) {
2972
- freshValidIds = cached.validIds;
2973
- } else {
2974
- freshValidIds = applyTransforms(
2975
- stateKey,
2976
- path,
2977
- meta?.transforms
2978
- );
2979
-
2980
- getGlobalStore
2981
- .getState()
2982
- .setTransformCache(stateKey, path, cacheKey, {
2983
- validIds: freshValidIds,
2984
- computedAt: Date.now(),
2985
- transforms: meta?.transforms || [],
2986
- });
2987
- }
2928
+ .setTransformCache(stateKey, path, cacheKey, {
2929
+ validIds: freshValidIds,
2930
+ computedAt: Date.now(),
2931
+ transforms: meta?.transforms || [],
2932
+ });
2933
+ }
2988
2934
 
2989
- const freshValues = getGlobalStore
2990
- .getState()
2991
- .getShadowValue(stateKeyPathKey, freshValidIds);
2935
+ const freshValues = getGlobalStore
2936
+ .getState()
2937
+ .getShadowValue(stateKeyPathKey, freshValidIds);
2992
2938
 
2993
- return {
2994
- validIds: freshValidIds,
2995
- arrayValues: freshValues || [],
2996
- };
2997
- }, [cacheKey, updateTrigger]);
2939
+ return {
2940
+ validIds: freshValidIds,
2941
+ arrayValues: freshValues || [],
2942
+ };
2943
+ }, [cacheKey, updateTrigger]);
2998
2944
 
2999
- useEffect(() => {
3000
- const unsubscribe = getGlobalStore
3001
- .getState()
3002
- .subscribeToPath(stateKeyPathKey, (e) => {
3003
- // A data change has occurred for the source array.
2945
+ useEffect(() => {
2946
+ const unsubscribe = getGlobalStore
2947
+ .getState()
2948
+ .subscribeToPath(stateKeyPathKey, (e) => {
2949
+ // A data change has occurred for the source array.
3004
2950
 
3005
- if (e.type === 'GET_SELECTED') {
3006
- return;
3007
- }
3008
- const shadowMeta = getGlobalStore
3009
- .getState()
3010
- .getShadowMetadata(stateKey, path);
3011
-
3012
- const caches = shadowMeta?.transformCaches;
3013
- if (caches) {
3014
- // Iterate over ALL keys in the cache map.
3015
- for (const key of caches.keys()) {
3016
- // If the key belongs to this component instance, delete it.
3017
- // This purges caches for 'sort by name', 'sort by score', etc.
3018
- if (key.startsWith(componentId)) {
3019
- caches.delete(key);
3020
- }
2951
+ if (e.type === 'GET_SELECTED') {
2952
+ return;
2953
+ }
2954
+ const shadowMeta = getGlobalStore
2955
+ .getState()
2956
+ .getShadowMetadata(stateKey, path);
2957
+
2958
+ const caches = shadowMeta?.transformCaches;
2959
+ if (caches) {
2960
+ // Iterate over ALL keys in the cache map.
2961
+ for (const key of caches.keys()) {
2962
+ // If the key belongs to this component instance, delete it.
2963
+ // This purges caches for 'sort by name', 'sort by score', etc.
2964
+ if (key.startsWith(componentId)) {
2965
+ caches.delete(key);
3021
2966
  }
3022
2967
  }
2968
+ }
3023
2969
 
3024
- if (
3025
- e.type === 'INSERT' ||
3026
- e.type === 'REMOVE' ||
3027
- e.type === 'CLEAR_SELECTION'
3028
- ) {
3029
- forceUpdate({});
3030
- }
3031
- });
3032
-
3033
- return () => {
3034
- unsubscribe();
3035
- };
2970
+ if (
2971
+ e.type === 'INSERT' ||
2972
+ e.type === 'REMOVE' ||
2973
+ e.type === 'CLEAR_SELECTION'
2974
+ ) {
2975
+ forceUpdate({});
2976
+ }
2977
+ });
3036
2978
 
3037
- // This effect's logic now depends on the componentId to perform the purge.
3038
- }, [componentId, stateKeyPathKey]);
2979
+ return () => {
2980
+ unsubscribe();
2981
+ };
3039
2982
 
3040
- if (!Array.isArray(arrayValues)) {
3041
- return null;
3042
- }
2983
+ // This effect's logic now depends on the componentId to perform the purge.
2984
+ }, [componentId, stateKeyPathKey]);
3043
2985
 
3044
- const arraySetter = rebuildStateShape({
3045
- currentState: arrayValues as any,
3046
- path,
3047
- componentId: componentId!,
3048
- meta: {
3049
- ...meta,
3050
- validIds: validIds,
3051
- },
3052
- });
2986
+ if (!Array.isArray(arrayValues)) {
2987
+ return null;
2988
+ }
3053
2989
 
3054
- return (
3055
- <>
3056
- {arrayValues.map((item, localIndex) => {
3057
- const itemKey = validIds[localIndex];
2990
+ const arraySetter = rebuildStateShape({
2991
+ path,
2992
+ componentId: componentId!,
2993
+ meta: {
2994
+ ...meta,
2995
+ validIds: validIds,
2996
+ },
2997
+ });
3058
2998
 
3059
- if (!itemKey) {
3060
- return null;
3061
- }
2999
+ return (
3000
+ <>
3001
+ {arrayValues.map((item, localIndex) => {
3002
+ const itemKey = validIds[localIndex];
3062
3003
 
3063
- let itemComponentId =
3064
- componentIdsRef.current.get(itemKey);
3065
- if (!itemComponentId) {
3066
- itemComponentId = uuidv4();
3067
- componentIdsRef.current.set(itemKey, itemComponentId);
3068
- }
3004
+ if (!itemKey) {
3005
+ return null;
3006
+ }
3069
3007
 
3070
- const itemPath = itemKey.split('.').slice(1);
3008
+ let itemComponentId = componentIdsRef.current.get(itemKey);
3009
+ if (!itemComponentId) {
3010
+ itemComponentId = uuidv4();
3011
+ componentIdsRef.current.set(itemKey, itemComponentId);
3012
+ }
3071
3013
 
3072
- return createElement(MemoizedCogsItemWrapper, {
3073
- key: itemKey,
3074
- stateKey,
3075
- itemComponentId,
3076
- itemPath,
3077
- localIndex,
3078
- arraySetter,
3079
- rebuildStateShape,
3080
- renderFn: callbackfn,
3081
- });
3082
- })}
3083
- </>
3084
- );
3085
- };
3014
+ const itemPath = itemKey.split('.').slice(1);
3086
3015
 
3087
- return <StateListWrapper />;
3088
- };
3089
- }
3090
- if (prop === 'stateFlattenOn') {
3091
- return (fieldName: string) => {
3092
- const arrayToMap = currentState as any[];
3093
- shapeCache.clear();
3094
- stateVersion++;
3095
- const flattenedResults = arrayToMap.flatMap(
3096
- (val: any) => val[fieldName] ?? []
3016
+ return createElement(MemoizedCogsItemWrapper, {
3017
+ key: itemKey,
3018
+ stateKey,
3019
+ itemComponentId,
3020
+ itemPath,
3021
+ localIndex,
3022
+ arraySetter,
3023
+ rebuildStateShape,
3024
+ renderFn: callbackfn,
3025
+ });
3026
+ })}
3027
+ </>
3097
3028
  );
3098
- return rebuildStateShape({
3099
- currentState: flattenedResults as any,
3100
- path: [...path, '[*]', fieldName],
3101
- componentId: componentId!,
3102
- meta,
3103
- });
3104
3029
  };
3105
- }
3106
- if (prop === 'index') {
3107
- return (index: number) => {
3108
- const arrayKeys = getGlobalStore
3109
- .getState()
3110
- .getShadowMetadata(stateKey, path)
3111
- ?.arrayKeys?.filter(
3112
- (key) =>
3113
- !meta?.validIds ||
3114
- (meta?.validIds && meta?.validIds?.includes(key))
3115
- );
3116
- const itemId = arrayKeys?.[index];
3117
- if (!itemId) return undefined;
3118
- const value = getGlobalStore
3119
- .getState()
3120
- .getShadowValue(itemId, meta?.validIds);
3121
- const state = rebuildStateShape({
3122
- currentState: value,
3123
- path: itemId.split('.').slice(1) as string[],
3124
- componentId: componentId!,
3125
- meta,
3126
- });
3127
- return state;
3128
- };
3129
- }
3130
- if (prop === 'last') {
3131
- return () => {
3132
- const currentArray = getGlobalStore
3133
- .getState()
3134
- .getShadowValue(stateKey, path) as any[];
3135
- if (currentArray.length === 0) return undefined;
3136
- const lastIndex = currentArray.length - 1;
3137
- const lastValue = currentArray[lastIndex];
3138
- const newPath = [...path, lastIndex.toString()];
3139
- return rebuildStateShape({
3140
- currentState: lastValue,
3141
- path: newPath,
3142
- componentId: componentId!,
3143
- meta,
3144
- });
3145
- };
3146
- }
3147
- if (prop === 'insert') {
3148
- return (
3149
- payload: InsertParams<InferArrayElement<T>>,
3150
- index?: number
3151
- ) => {
3152
- effectiveSetState(payload as any, path, { updateType: 'insert' });
3153
- return rebuildStateShape({
3154
- currentState: getGlobalStore
3155
- .getState()
3156
- .getShadowValue(stateKey, path),
3157
- path,
3158
- componentId: componentId!,
3159
- meta,
3160
- });
3161
- };
3162
- }
3163
- if (prop === 'uniqueInsert') {
3164
- return (
3165
- payload: UpdateArg<T>,
3166
- fields?: (keyof InferArrayElement<T>)[],
3167
- onMatch?: (existingItem: any) => any
3168
- ) => {
3169
- const currentArray = getGlobalStore
3170
- .getState()
3171
- .getShadowValue(stateKey, path) as any[];
3172
- const newValue = isFunction<T>(payload)
3173
- ? payload(currentArray as any)
3174
- : (payload as any);
3175
-
3176
- let matchedItem: any = null;
3177
- const isUnique = !currentArray.some((item) => {
3178
- const isMatch = fields
3179
- ? fields.every((field) =>
3180
- isDeepEqual(item[field], newValue[field])
3181
- )
3182
- : isDeepEqual(item, newValue);
3183
- if (isMatch) matchedItem = item;
3184
- return isMatch;
3185
- });
3186
-
3187
- if (isUnique) {
3188
- invalidateCachePath(path);
3189
- effectiveSetState(newValue, path, { updateType: 'insert' });
3190
- } else if (onMatch && matchedItem) {
3191
- const updatedItem = onMatch(matchedItem);
3192
- const updatedArray = currentArray.map((item) =>
3193
- isDeepEqual(item, matchedItem) ? updatedItem : item
3194
- );
3195
- invalidateCachePath(path);
3196
- effectiveSetState(updatedArray as any, path, {
3197
- updateType: 'update',
3198
- });
3199
- }
3200
- };
3201
- }
3202
-
3203
- if (prop === 'cut') {
3204
- return (index?: number, options?: { waitForSync?: boolean }) => {
3205
- const validKeys =
3206
- meta?.validIds ??
3207
- getGlobalStore.getState().getShadowMetadata(stateKey, path)
3208
- ?.arrayKeys;
3209
3030
 
3210
- if (!validKeys || validKeys.length === 0) return;
3031
+ return <StateListWrapper />;
3032
+ };
3033
+ }
3034
+ if (prop === 'stateFlattenOn') {
3035
+ return (fieldName: string) => {
3036
+ const currentState = getGlobalStore
3037
+ .getState()
3038
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
3039
+ if (!Array.isArray(currentState)) return []; // Guard clause
3040
+ const arrayToMap = currentState as any[];
3211
3041
 
3212
- const indexToCut =
3213
- index == -1
3214
- ? validKeys.length - 1
3215
- : index !== undefined
3216
- ? index
3217
- : validKeys.length - 1;
3042
+ stateVersion++;
3043
+ const flattenedResults = arrayToMap.flatMap(
3044
+ (val: any) => val[fieldName] ?? []
3045
+ );
3046
+ return rebuildStateShape({
3047
+ path: [...path, '[*]', fieldName],
3048
+ componentId: componentId!,
3049
+ meta,
3050
+ });
3051
+ };
3052
+ }
3053
+ if (prop === 'index') {
3054
+ return (index: number) => {
3055
+ const arrayKeys = getGlobalStore
3056
+ .getState()
3057
+ .getShadowMetadata(stateKey, path)
3058
+ ?.arrayKeys?.filter(
3059
+ (key) =>
3060
+ !meta?.validIds ||
3061
+ (meta?.validIds && meta?.validIds?.includes(key))
3062
+ );
3063
+ const itemId = arrayKeys?.[index];
3064
+ if (!itemId) return undefined;
3065
+ const value = getGlobalStore
3066
+ .getState()
3067
+ .getShadowValue(itemId, meta?.validIds);
3068
+ const state = rebuildStateShape({
3069
+ path: itemId.split('.').slice(1) as string[],
3070
+ componentId: componentId!,
3071
+ meta,
3072
+ });
3073
+ return state;
3074
+ };
3075
+ }
3076
+ if (prop === 'last') {
3077
+ return () => {
3078
+ const currentArray = getGlobalStore
3079
+ .getState()
3080
+ .getShadowValue(stateKey, path) as any[];
3081
+ if (currentArray.length === 0) return undefined;
3082
+ const lastIndex = currentArray.length - 1;
3083
+ const lastValue = currentArray[lastIndex];
3084
+ const newPath = [...path, lastIndex.toString()];
3085
+ return rebuildStateShape({
3086
+ path: newPath,
3087
+ componentId: componentId!,
3088
+ meta,
3089
+ });
3090
+ };
3091
+ }
3092
+ if (prop === 'insert') {
3093
+ return (
3094
+ payload: InsertParams<InferArrayElement<T>>,
3095
+ index?: number
3096
+ ) => {
3097
+ effectiveSetState(payload as any, path, { updateType: 'insert' });
3098
+ return rebuildStateShape({
3099
+ path,
3100
+ componentId: componentId!,
3101
+ meta,
3102
+ });
3103
+ };
3104
+ }
3105
+ if (prop === 'uniqueInsert') {
3106
+ return (
3107
+ payload: UpdateArg<T>,
3108
+ fields?: (keyof InferArrayElement<T>)[],
3109
+ onMatch?: (existingItem: any) => any
3110
+ ) => {
3111
+ const currentArray = getGlobalStore
3112
+ .getState()
3113
+ .getShadowValue(stateKey, path) as any[];
3114
+ const newValue = isFunction<T>(payload)
3115
+ ? payload(currentArray as any)
3116
+ : (payload as any);
3117
+
3118
+ let matchedItem: any = null;
3119
+ const isUnique = !currentArray.some((item) => {
3120
+ const isMatch = fields
3121
+ ? fields.every((field) =>
3122
+ isDeepEqual(item[field], newValue[field])
3123
+ )
3124
+ : isDeepEqual(item, newValue);
3125
+ if (isMatch) matchedItem = item;
3126
+ return isMatch;
3127
+ });
3218
3128
 
3219
- const fullIdToCut = validKeys[indexToCut];
3220
- if (!fullIdToCut) return; // Index out of bounds
3129
+ if (isUnique) {
3130
+ effectiveSetState(newValue, path, { updateType: 'insert' });
3131
+ } else if (onMatch && matchedItem) {
3132
+ const updatedItem = onMatch(matchedItem);
3133
+ const updatedArray = currentArray.map((item) =>
3134
+ isDeepEqual(item, matchedItem) ? updatedItem : item
3135
+ );
3221
3136
 
3222
- const pathForCut = fullIdToCut.split('.').slice(1);
3223
- effectiveSetState(currentState, pathForCut, {
3224
- updateType: 'cut',
3137
+ effectiveSetState(updatedArray as any, path, {
3138
+ updateType: 'update',
3225
3139
  });
3226
- };
3227
- }
3228
- if (prop === 'cutSelected') {
3229
- return () => {
3230
- const validKeys = applyTransforms(
3231
- stateKey,
3232
- path,
3233
- meta?.transforms
3234
- );
3140
+ }
3141
+ };
3142
+ }
3235
3143
 
3236
- if (!validKeys || validKeys.length === 0) return;
3144
+ if (prop === 'cut') {
3145
+ return (index?: number, options?: { waitForSync?: boolean }) => {
3146
+ const currentState = getGlobalStore
3147
+ .getState()
3148
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
3149
+ const validKeys =
3150
+ meta?.validIds ??
3151
+ getGlobalStore.getState().getShadowMetadata(stateKey, path)
3152
+ ?.arrayKeys;
3153
+
3154
+ if (!validKeys || validKeys.length === 0) return;
3155
+
3156
+ const indexToCut =
3157
+ index == -1
3158
+ ? validKeys.length - 1
3159
+ : index !== undefined
3160
+ ? index
3161
+ : validKeys.length - 1;
3162
+
3163
+ const fullIdToCut = validKeys[indexToCut];
3164
+ if (!fullIdToCut) return; // Index out of bounds
3165
+
3166
+ const pathForCut = fullIdToCut.split('.').slice(1);
3167
+ effectiveSetState(currentState, pathForCut, {
3168
+ updateType: 'cut',
3169
+ });
3170
+ };
3171
+ }
3172
+ if (prop === 'cutSelected') {
3173
+ return () => {
3174
+ const validKeys = applyTransforms(stateKey, path, meta?.transforms);
3175
+ const currentState = getGlobalStore
3176
+ .getState()
3177
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
3178
+ if (!validKeys || validKeys.length === 0) return;
3237
3179
 
3238
- const indexKeyToCut = getGlobalStore
3239
- .getState()
3240
- .selectedIndicesMap.get(stateKeyPathKey);
3180
+ const indexKeyToCut = getGlobalStore
3181
+ .getState()
3182
+ .selectedIndicesMap.get(stateKeyPathKey);
3241
3183
 
3242
- let indexToCut = validKeys.findIndex(
3243
- (key) => key === indexKeyToCut
3244
- );
3184
+ let indexToCut = validKeys.findIndex(
3185
+ (key) => key === indexKeyToCut
3186
+ );
3245
3187
 
3246
- const pathForCut = validKeys[
3247
- indexToCut == -1 ? validKeys.length - 1 : indexToCut
3248
- ]
3249
- ?.split('.')
3250
- .slice(1);
3251
- getGlobalStore
3252
- .getState()
3253
- .clearSelectedIndex({ arrayKey: stateKeyPathKey });
3254
- const parentPath = pathForCut?.slice(0, -1)!;
3255
- notifySelectionComponents(stateKey, parentPath);
3256
- effectiveSetState(currentState, pathForCut!, {
3257
- updateType: 'cut',
3258
- });
3259
- };
3260
- }
3261
- if (prop === 'cutByValue') {
3262
- return (value: string | number | boolean) => {
3263
- // Step 1: Get the list of all unique keys for the current view.
3264
- const arrayMeta = getGlobalStore
3265
- .getState()
3266
- .getShadowMetadata(stateKey, path);
3267
- const relevantKeys = meta?.validIds ?? arrayMeta?.arrayKeys;
3188
+ const pathForCut = validKeys[
3189
+ indexToCut == -1 ? validKeys.length - 1 : indexToCut
3190
+ ]
3191
+ ?.split('.')
3192
+ .slice(1);
3193
+ getGlobalStore
3194
+ .getState()
3195
+ .clearSelectedIndex({ arrayKey: stateKeyPathKey });
3196
+ const parentPath = pathForCut?.slice(0, -1)!;
3197
+ notifySelectionComponents(stateKey, parentPath);
3198
+ effectiveSetState(currentState, pathForCut!, {
3199
+ updateType: 'cut',
3200
+ });
3201
+ };
3202
+ }
3203
+ if (prop === 'cutByValue') {
3204
+ return (value: string | number | boolean) => {
3205
+ // Step 1: Get the list of all unique keys for the current view.
3206
+ const arrayMeta = getGlobalStore
3207
+ .getState()
3208
+ .getShadowMetadata(stateKey, path);
3209
+ const relevantKeys = meta?.validIds ?? arrayMeta?.arrayKeys;
3268
3210
 
3269
- if (!relevantKeys) return;
3211
+ if (!relevantKeys) return;
3270
3212
 
3271
- let keyToCut: string | null = null;
3213
+ let keyToCut: string | null = null;
3272
3214
 
3273
- // Step 2: Iterate through the KEYS, get the value for each, and find the match.
3274
- for (const key of relevantKeys) {
3275
- const itemValue = getGlobalStore.getState().getShadowValue(key);
3276
- if (itemValue === value) {
3277
- keyToCut = key;
3278
- break; // We found the key, no need to search further.
3279
- }
3215
+ // Step 2: Iterate through the KEYS, get the value for each, and find the match.
3216
+ for (const key of relevantKeys) {
3217
+ const itemValue = getGlobalStore.getState().getShadowValue(key);
3218
+ if (itemValue === value) {
3219
+ keyToCut = key;
3220
+ break; // We found the key, no need to search further.
3280
3221
  }
3222
+ }
3281
3223
 
3282
- // Step 3: If we found a matching key, use it to perform the cut.
3283
- if (keyToCut) {
3284
- const itemPath = keyToCut.split('.').slice(1);
3285
- effectiveSetState(null as any, itemPath, { updateType: 'cut' });
3286
- }
3287
- };
3288
- }
3224
+ // Step 3: If we found a matching key, use it to perform the cut.
3225
+ if (keyToCut) {
3226
+ const itemPath = keyToCut.split('.').slice(1);
3227
+ effectiveSetState(null as any, itemPath, { updateType: 'cut' });
3228
+ }
3229
+ };
3230
+ }
3289
3231
 
3290
- if (prop === 'toggleByValue') {
3291
- return (value: string | number | boolean) => {
3292
- // Step 1: Get the list of all unique keys for the current view.
3293
- const arrayMeta = getGlobalStore
3294
- .getState()
3295
- .getShadowMetadata(stateKey, path);
3296
- const relevantKeys = meta?.validIds ?? arrayMeta?.arrayKeys;
3232
+ if (prop === 'toggleByValue') {
3233
+ return (value: string | number | boolean) => {
3234
+ // Step 1: Get the list of all unique keys for the current view.
3235
+ const arrayMeta = getGlobalStore
3236
+ .getState()
3237
+ .getShadowMetadata(stateKey, path);
3238
+ const relevantKeys = meta?.validIds ?? arrayMeta?.arrayKeys;
3297
3239
 
3298
- if (!relevantKeys) return;
3240
+ if (!relevantKeys) return;
3299
3241
 
3300
- let keyToCut: string | null = null;
3242
+ let keyToCut: string | null = null;
3301
3243
 
3302
- // Step 2: Iterate through the KEYS to find the one matching the value. This is the robust way.
3303
- for (const key of relevantKeys) {
3304
- const itemValue = getGlobalStore.getState().getShadowValue(key);
3305
- console.log('itemValue sdasdasdasd', itemValue);
3306
- if (itemValue === value) {
3307
- keyToCut = key;
3308
- break; // Found it!
3309
- }
3244
+ // Step 2: Iterate through the KEYS to find the one matching the value. This is the robust way.
3245
+ for (const key of relevantKeys) {
3246
+ const itemValue = getGlobalStore.getState().getShadowValue(key);
3247
+ console.log('itemValue sdasdasdasd', itemValue);
3248
+ if (itemValue === value) {
3249
+ keyToCut = key;
3250
+ break; // Found it!
3310
3251
  }
3252
+ }
3253
+ console.log('itemValue keyToCut', keyToCut);
3254
+ // Step 3: Act based on whether the key was found.
3255
+ if (keyToCut) {
3256
+ // Item exists, so we CUT it using its *actual* key.
3257
+ const itemPath = keyToCut.split('.').slice(1);
3311
3258
  console.log('itemValue keyToCut', keyToCut);
3312
- // Step 3: Act based on whether the key was found.
3313
- if (keyToCut) {
3314
- // Item exists, so we CUT it using its *actual* key.
3315
- const itemPath = keyToCut.split('.').slice(1);
3316
- console.log('itemValue keyToCut', keyToCut);
3317
- effectiveSetState(value as any, itemPath, {
3318
- updateType: 'cut',
3319
- });
3320
- } else {
3321
- // Item does not exist, so we INSERT it.
3322
- effectiveSetState(value as any, path, { updateType: 'insert' });
3323
- }
3324
- };
3325
- }
3326
- if (prop === 'findWith') {
3327
- return (
3328
- searchKey: keyof InferArrayElement<T>,
3329
- searchValue: any
3330
- ) => {
3331
- const arrayKeys = getGlobalStore
3332
- .getState()
3333
- .getShadowMetadata(stateKey, path)?.arrayKeys;
3259
+ effectiveSetState(value as any, itemPath, {
3260
+ updateType: 'cut',
3261
+ });
3262
+ } else {
3263
+ // Item does not exist, so we INSERT it.
3264
+ effectiveSetState(value as any, path, { updateType: 'insert' });
3265
+ }
3266
+ };
3267
+ }
3268
+ if (prop === 'findWith') {
3269
+ return (searchKey: keyof InferArrayElement<T>, searchValue: any) => {
3270
+ const arrayKeys = getGlobalStore
3271
+ .getState()
3272
+ .getShadowMetadata(stateKey, path)?.arrayKeys;
3334
3273
 
3335
- if (!arrayKeys) {
3336
- throw new Error('No array keys found for sorting');
3337
- }
3274
+ if (!arrayKeys) {
3275
+ throw new Error('No array keys found for sorting');
3276
+ }
3338
3277
 
3339
- let value = null;
3340
- let foundPath: string[] = [];
3278
+ let value = null;
3279
+ let foundPath: string[] = [];
3341
3280
 
3342
- for (const fullPath of arrayKeys) {
3343
- let shadowValue = getGlobalStore
3344
- .getState()
3345
- .getShadowValue(fullPath, meta?.validIds);
3346
- if (shadowValue && shadowValue[searchKey] === searchValue) {
3347
- value = shadowValue;
3348
- foundPath = fullPath.split('.').slice(1);
3349
- break;
3350
- }
3281
+ for (const fullPath of arrayKeys) {
3282
+ let shadowValue = getGlobalStore
3283
+ .getState()
3284
+ .getShadowValue(fullPath, meta?.validIds);
3285
+ if (shadowValue && shadowValue[searchKey] === searchValue) {
3286
+ value = shadowValue;
3287
+ foundPath = fullPath.split('.').slice(1);
3288
+ break;
3351
3289
  }
3290
+ }
3352
3291
 
3353
- return rebuildStateShape({
3354
- currentState: value as any,
3355
- path: foundPath,
3356
- componentId: componentId!,
3357
- meta,
3358
- });
3359
- };
3360
- }
3292
+ return rebuildStateShape({
3293
+ path: foundPath,
3294
+ componentId: componentId!,
3295
+ meta,
3296
+ });
3297
+ };
3361
3298
  }
3362
3299
 
3363
- if (prop === 'cut') {
3300
+ if (prop === 'cutThis') {
3364
3301
  let shadowValue = getGlobalStore
3365
3302
  .getState()
3366
3303
  .getShadowValue(path.join('.'));
@@ -3698,7 +3635,9 @@ function createProxyHandler<T>(
3698
3635
  const currentValueAtPath = getGlobalStore
3699
3636
  .getState()
3700
3637
  .getShadowValue([stateKey, ...path].join('.'));
3701
-
3638
+ const currentState = getGlobalStore
3639
+ .getState()
3640
+ .getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
3702
3641
  console.log('currentValueAtPath', currentValueAtPath);
3703
3642
  if (typeof currentState != 'boolean') {
3704
3643
  throw new Error('toggle() can only be used on boolean values');
@@ -3728,7 +3667,6 @@ function createProxyHandler<T>(
3728
3667
  .getState()
3729
3668
  .getShadowValue(stateKey, nextPath);
3730
3669
  return rebuildStateShape({
3731
- currentState: nextValue,
3732
3670
  path: nextPath,
3733
3671
  componentId: componentId!,
3734
3672
  meta,
@@ -3737,10 +3675,7 @@ function createProxyHandler<T>(
3737
3675
  };
3738
3676
 
3739
3677
  const proxyInstance = new Proxy(baseFunction, handler);
3740
- shapeCache.set(cacheKey, {
3741
- proxy: proxyInstance,
3742
- stateVersion: stateVersion,
3743
- });
3678
+ proxyCache.set(cacheKey, proxyInstance);
3744
3679
  return proxyInstance;
3745
3680
  }
3746
3681
 
@@ -3766,11 +3701,10 @@ function createProxyHandler<T>(
3766
3701
  getGlobalStore.getState().initialStateGlobal[stateKey];
3767
3702
 
3768
3703
  getGlobalStore.getState().clearSelectedIndexesForState(stateKey);
3769
- shapeCache.clear();
3704
+
3770
3705
  stateVersion++;
3771
3706
  getGlobalStore.getState().initializeShadowState(stateKey, initialState);
3772
- const newProxy = rebuildStateShape({
3773
- currentState: initialState,
3707
+ rebuildStateShape({
3774
3708
  path: [],
3775
3709
  componentId: componentId!,
3776
3710
  });
@@ -3797,7 +3731,6 @@ function createProxyHandler<T>(
3797
3731
  return initialState;
3798
3732
  },
3799
3733
  updateInitialState: (newState: T) => {
3800
- shapeCache.clear();
3801
3734
  stateVersion++;
3802
3735
 
3803
3736
  const newUpdaterState = createProxyHandler(
@@ -3839,7 +3772,6 @@ function createProxyHandler<T>(
3839
3772
  },
3840
3773
  };
3841
3774
  const returnShape = rebuildStateShape({
3842
- currentState: getGlobalStore.getState().getShadowValue(stateKey, []),
3843
3775
  componentId,
3844
3776
  path: [],
3845
3777
  });