cogsbox-state 0.5.427 → 0.5.428

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
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { ulid } from "ulid";
2
+
3
3
  import {
4
4
  createElement,
5
5
  startTransition,
@@ -34,15 +34,11 @@ import superjson from "superjson";
34
34
  import { v4 as uuidv4 } from "uuid";
35
35
  import { z } from "zod";
36
36
 
37
- import {
38
- formRefStore,
39
- getGlobalStore,
40
- type ComponentsType,
41
- type ItemMeta,
42
- } from "./store.js";
37
+ import { formRefStore, getGlobalStore, type ComponentsType } from "./store.js";
43
38
  import { useCogsConfig } from "./CogsStateClient.js";
44
39
  import { applyPatch } from "fast-json-patch";
45
40
  import useMeasure from "react-use-measure";
41
+ import { ulid } from "ulid";
46
42
 
47
43
  type Prettify<T> = { [K in keyof T]: T[K] } & {};
48
44
 
@@ -568,7 +564,7 @@ export function addStateOptions<T extends unknown>(
568
564
  return { initialState: initialState, formElements, validation } as T;
569
565
  }
570
566
 
571
- export const createCogsState = <State extends Record<string, unknown>>(
567
+ export const createCogsState = <State extends Record<StateKeys, unknown>>(
572
568
  initialState: State,
573
569
  opt?: { formElements?: FormsElementsType; validation?: ValidationOptionsType }
574
570
  ) => {
@@ -1096,7 +1092,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
1096
1092
  }
1097
1093
  }
1098
1094
 
1099
- console.log("shadowState", store.shadowStateStore);
1100
1095
  if (
1101
1096
  updateObj.updateType === "update" &&
1102
1097
  (validationKey || latestInitialOptionsRef.current?.validation?.key) &&
@@ -1123,22 +1118,20 @@ export function useCogsStateFn<TStateObject extends unknown>(
1123
1118
  updateObj.updateType === "insert" &&
1124
1119
  latestInitialOptionsRef.current?.validation?.key
1125
1120
  ) {
1126
- let getValidation = getValidationErrors(
1121
+ const getValidation = getValidationErrors(
1127
1122
  latestInitialOptionsRef.current?.validation?.key +
1128
1123
  "." +
1129
1124
  arrayWithoutIndex.join(".")
1130
1125
  );
1131
1126
 
1132
- //TODO this is untested its supposed to cahnge teh validation errors alreaady stored when a new entry is push
1133
-
1134
- getValidation.filter(([k, v]) => {
1127
+ getValidation.filter((k) => {
1135
1128
  let length = k?.split(".").length;
1129
+ const v = ""; // Placeholder as `v` is not used from getValidationErrors
1136
1130
 
1137
1131
  if (
1138
1132
  k == arrayWithoutIndex.join(".") &&
1139
1133
  length == arrayWithoutIndex.length - 1
1140
1134
  ) {
1141
- // console.log(length, pathWithoutIndex.length);
1142
1135
  let newKey = k + "." + arrayWithoutIndex;
1143
1136
  removeValidationError(k!);
1144
1137
  addValidationError(newKey, v!);
@@ -1147,7 +1140,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
1147
1140
  }
1148
1141
 
1149
1142
  const stateEntry = store.stateComponents.get(thisKey);
1150
- console.log("stateEntry >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", stateEntry);
1151
1143
  if (stateEntry) {
1152
1144
  const changedPaths = getDifferences(prevValue, payload);
1153
1145
  const changedPathsSet = new Set(changedPaths);
@@ -1164,7 +1156,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
1164
1156
  const reactiveTypes = Array.isArray(component.reactiveType)
1165
1157
  ? component.reactiveType
1166
1158
  : [component.reactiveType || "component"];
1167
- console.log("component", component);
1159
+
1168
1160
  if (reactiveTypes.includes("none")) continue;
1169
1161
  if (reactiveTypes.includes("all")) {
1170
1162
  component.forceUpdate();
@@ -1238,17 +1230,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
1238
1230
  }
1239
1231
  const timeStamp = Date.now();
1240
1232
 
1241
- path = path.map((p, i) => {
1242
- const arrayPath = path.slice(0, -1);
1243
- const arrayValue = getNestedValue(payload, arrayPath);
1244
-
1245
- return i === path.length - 1 &&
1246
- ["insert", "cut"].includes(updateObj.updateType)
1247
- ? (arrayValue.length - 1).toString()
1248
- : p;
1249
- });
1250
-
1251
- const { oldValue, newValue } = getUpdateValues(
1233
+ let { oldValue, newValue } = getUpdateValues(
1252
1234
  updateObj.updateType,
1253
1235
  prevValue,
1254
1236
  payload,
@@ -1265,56 +1247,59 @@ export function useCogsStateFn<TStateObject extends unknown>(
1265
1247
  } satisfies UpdateTypeDetail;
1266
1248
 
1267
1249
  switch (updateObj.updateType) {
1268
- case "update":
1269
- // For updates, just mirror the structure at the path
1270
- store.updateShadowAtPath(thisKey, path, payload);
1271
- break;
1272
-
1273
- case "insert":
1274
- // For array insert, create a new metadata object with a ULID.
1250
+ case "insert": {
1275
1251
  const parentPath = path.slice(0, -1);
1252
+ const idSegment = path[path.length - 1]!; // e.g., 'id:xyz'
1253
+ const targetId = idSegment.split(":")[1];
1254
+ const newArray = getNestedValue(payload, parentPath);
1276
1255
 
1277
- // THE CHANGE IS HERE: Instead of `newValue`, we insert our metadata object.
1278
- // The `newValue` from your code is the new item itself, which might have an `id` or `key`.
1279
- const newId = newValue.id ?? newValue.key ?? ulid();
1280
- const newMeta = { _cogsId: newId };
1256
+ newValue = newArray.find((item: any) => item.id == targetId);
1257
+ oldValue = null;
1281
1258
 
1282
- store.insertShadowArrayElement(thisKey, parentPath, newMeta);
1259
+ store.insertShadowArrayElement(thisKey, parentPath, newValue);
1283
1260
  break;
1261
+ }
1284
1262
 
1285
- case "cut":
1286
- // For array cut, remove element from shadow
1287
- // THIS PART DOES NOT NEED TO CHANGE. It correctly removes the item
1288
- // at the specified index from both the data and shadow arrays.
1289
- const arrayPath = path.slice(0, -1);
1290
- const index = parseInt(path[path.length - 1]!);
1291
- store.removeShadowArrayElement(thisKey, arrayPath, index);
1263
+ case "cut": {
1264
+ oldValue = getNestedValue(prevValue, path);
1265
+ newValue = null;
1266
+
1267
+ store.removeShadowArrayElement(thisKey, path);
1268
+ break;
1269
+ }
1270
+
1271
+ case "update": {
1272
+ oldValue = getNestedValue(prevValue, path);
1273
+ newValue = getNestedValue(payload, path);
1274
+
1275
+ const shadowPath = path.map((p, i) => {
1276
+ const currentSubPath = path.slice(0, i + 1);
1277
+ const subValue = getNestedValue(payload, currentSubPath);
1278
+ return subValue?.id ? `id:${subValue.id}` : p;
1279
+ });
1280
+ store.updateShadowAtPath(thisKey, shadowPath, newValue);
1292
1281
  break;
1282
+ }
1293
1283
  }
1294
1284
 
1295
1285
  setStateLog(thisKey, (prevLogs) => {
1296
1286
  const logs = [...(prevLogs ?? []), newUpdate];
1287
+ const aggregatedLogs = new Map<string, typeof newUpdate>();
1297
1288
 
1298
- // Aggregate the updates by stateKey and path
1299
- const aggregatedLogs = logs.reduce((acc, log) => {
1289
+ logs.forEach((log) => {
1300
1290
  const uniqueKey = `${log.stateKey}:${JSON.stringify(log.path)}`;
1301
- const existing = acc.get(uniqueKey);
1291
+ const existing = aggregatedLogs.get(uniqueKey);
1302
1292
 
1303
1293
  if (existing) {
1304
- // Update the existing entry with the most recent details
1305
1294
  existing.timeStamp = Math.max(existing.timeStamp, log.timeStamp);
1306
- existing.newValue = log.newValue; // Overwrite with the latest value
1307
- existing.oldValue = existing.oldValue ?? log.oldValue; // Retain the initial oldValue
1308
- existing.updateType = log.updateType; // Update to the most recent type
1295
+ existing.newValue = log.newValue;
1296
+ existing.oldValue = existing.oldValue ?? log.oldValue;
1297
+ existing.updateType = log.updateType;
1309
1298
  } else {
1310
- // Add the log if no existing match is found
1311
- acc.set(uniqueKey, { ...(log as any) });
1299
+ aggregatedLogs.set(uniqueKey, { ...(log as any) });
1312
1300
  }
1301
+ });
1313
1302
 
1314
- return acc;
1315
- }, new Map<string, typeof newUpdate>());
1316
-
1317
- // Convert the aggregated map back to an array
1318
1303
  return Array.from(aggregatedLogs.values());
1319
1304
  });
1320
1305
 
@@ -1367,7 +1352,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
1367
1352
  }
1368
1353
 
1369
1354
  const updaterFinal = useMemo(() => {
1370
- // Create proxy with baseObject as target
1371
1355
  return createProxyHandler<TStateObject>(
1372
1356
  thisKey,
1373
1357
  effectiveSetState,
@@ -1388,7 +1372,6 @@ function createProxyHandler<T>(
1388
1372
  componentId: string,
1389
1373
  sessionId?: string
1390
1374
  ): StateObject<T> {
1391
- // ADDED: Enhanced cache with versioning
1392
1375
  type CacheEntry = {
1393
1376
  proxy: any;
1394
1377
  stateVersion: number;
@@ -1396,7 +1379,6 @@ function createProxyHandler<T>(
1396
1379
  const shapeCache = new Map<string, CacheEntry>();
1397
1380
  let stateVersion = 0;
1398
1381
 
1399
- // ADDED: Cache invalidation helper
1400
1382
  const invalidateCachePath = (path: string[]) => {
1401
1383
  const pathKey = path.join(".");
1402
1384
  for (const [key] of shapeCache) {
@@ -1428,9 +1410,8 @@ function createProxyHandler<T>(
1428
1410
 
1429
1411
  const initialState =
1430
1412
  getGlobalStore.getState().initialStateGlobal[stateKey];
1431
-
1413
+ getGlobalStore.getState().initializeShadowState(stateKey, initialState);
1432
1414
  getGlobalStore.getState().clearSelectedIndexesForState(stateKey);
1433
- // ADDED: Clear cache on revert
1434
1415
  shapeCache.clear();
1435
1416
  stateVersion++;
1436
1417
 
@@ -1460,7 +1441,6 @@ function createProxyHandler<T>(
1460
1441
  return initialState;
1461
1442
  },
1462
1443
  updateInitialState: (newState: T) => {
1463
- // ADDED: Clear cache on initial state update
1464
1444
  shapeCache.clear();
1465
1445
  stateVersion++;
1466
1446
 
@@ -1513,14 +1493,20 @@ function createProxyHandler<T>(
1513
1493
  },
1514
1494
  };
1515
1495
 
1496
+ function getOrderedIds(arrayPath: string[]): string[] | null {
1497
+ const arrayKey = [stateKey, ...arrayPath].join(".");
1498
+ const arrayMeta = getGlobalStore.getState().shadowStateStore.get(arrayKey);
1499
+ return arrayMeta?.arrayKeys || null;
1500
+ }
1501
+
1516
1502
  function rebuildStateShape(
1517
1503
  currentState: T,
1518
1504
  path: string[] = [],
1519
- meta?: { filtered?: string[][]; validIndices?: number[] }
1505
+ meta?: {
1506
+ validIds?: string[];
1507
+ }
1520
1508
  ): any {
1521
1509
  const cacheKey = path.map(String).join(".");
1522
-
1523
- // MODIFIED: Cache check with version
1524
1510
  const cachedEntry = shapeCache.get(cacheKey);
1525
1511
 
1526
1512
  type CallableStateObject<T> = {
@@ -1533,24 +1519,16 @@ function createProxyHandler<T>(
1533
1519
  return getGlobalStore().getNestedState(stateKey, path);
1534
1520
  } as unknown as CallableStateObject<T>;
1535
1521
 
1536
- // Copy properties from baseObj to the function with type assertion
1537
1522
  Object.keys(baseObj).forEach((key) => {
1538
1523
  (baseFunction as any)[key] = (baseObj as any)[key];
1539
1524
  });
1540
1525
 
1541
1526
  const handler = {
1542
1527
  apply(target: any, thisArg: any, args: any[]) {
1543
- console.log(
1544
- `PROXY APPLY TRAP HIT: stateKey=${stateKey}, path=${path.join(".")}`
1545
- ); // <--- ADD LOGGING
1546
- console.trace("Apply trap stack trace");
1547
1528
  return getGlobalStore().getNestedState(stateKey, path);
1548
1529
  },
1549
1530
 
1550
1531
  get(target: any, prop: string) {
1551
- if (meta?.validIndices && !Array.isArray(currentState)) {
1552
- meta = { ...meta, validIndices: undefined };
1553
- }
1554
1532
  const mutationMethods = new Set([
1555
1533
  "insert",
1556
1534
  "cut",
@@ -1582,51 +1560,40 @@ function createProxyHandler<T>(
1582
1560
  !mutationMethods.has(prop)
1583
1561
  ) {
1584
1562
  const fullComponentId = `${stateKey}////${componentId}`;
1585
- // console.log("adding path", fullComponentId, path, prop);
1586
1563
  const stateEntry = getGlobalStore
1587
1564
  .getState()
1588
1565
  .stateComponents.get(stateKey);
1589
1566
 
1590
1567
  if (stateEntry) {
1591
1568
  const component = stateEntry.components.get(fullComponentId);
1592
-
1593
- if (component) {
1594
- // Mark as initialized immediately to prevent re-processing
1595
-
1596
- // Now do the path tracking logic ONCE
1597
- if (!component.paths.has("")) {
1598
- const currentPath = path.join(".");
1599
- let needsAdd = true;
1600
- for (const existingPath of component.paths) {
1601
- if (
1602
- currentPath.startsWith(existingPath) &&
1603
- (currentPath === existingPath ||
1604
- currentPath[existingPath.length] === ".")
1605
- ) {
1606
- needsAdd = false;
1607
- break;
1608
- }
1609
- }
1610
-
1611
- if (needsAdd) {
1612
- component.paths.add(currentPath);
1569
+ if (component && !component.paths.has("")) {
1570
+ const currentPath = path.join(".");
1571
+ let needsAdd = true;
1572
+ for (const existingPath of component.paths) {
1573
+ if (
1574
+ currentPath.startsWith(existingPath) &&
1575
+ (currentPath === existingPath ||
1576
+ currentPath[existingPath.length] === ".")
1577
+ ) {
1578
+ needsAdd = false;
1579
+ break;
1613
1580
  }
1614
1581
  }
1582
+ if (needsAdd) {
1583
+ component.paths.add(currentPath);
1584
+ }
1615
1585
  }
1616
1586
  }
1617
1587
  }
1618
1588
  if (prop === "getDifferences") {
1619
- return () => {
1620
- const differences = getDifferences(
1589
+ return () =>
1590
+ getDifferences(
1621
1591
  getGlobalStore.getState().cogsStateStore[stateKey],
1622
1592
  getGlobalStore.getState().initialStateGlobal[stateKey]
1623
1593
  );
1624
- return differences;
1625
- };
1626
1594
  }
1627
1595
  if (prop === "sync" && path.length === 0) {
1628
1596
  return async function () {
1629
- // Get the options for this state key
1630
1597
  const options = getGlobalStore
1631
1598
  .getState()
1632
1599
  .getInitialOptions(stateKey);
@@ -1637,100 +1604,64 @@ function createProxyHandler<T>(
1637
1604
  return { success: false, error: `No mutation defined` };
1638
1605
  }
1639
1606
 
1640
- // Get the root state
1641
1607
  const state = getGlobalStore
1642
1608
  .getState()
1643
1609
  .getNestedState(stateKey, []);
1644
-
1645
- // Get validation key
1646
1610
  const validationKey = options?.validation?.key;
1647
1611
 
1648
1612
  try {
1649
- // Execute the mutation action
1650
1613
  const response = await sync.action(state);
1651
-
1652
- // Handle validation errors
1653
1614
  if (
1654
1615
  response &&
1655
1616
  !response.success &&
1656
1617
  response.errors &&
1657
1618
  validationKey
1658
1619
  ) {
1659
- // Clear existing errors
1660
1620
  getGlobalStore.getState().removeValidationError(validationKey);
1661
-
1662
- // Add new validation errors
1663
1621
  response.errors.forEach((error) => {
1664
1622
  const errorPath = [validationKey, ...error.path].join(".");
1665
-
1666
1623
  getGlobalStore
1667
1624
  .getState()
1668
1625
  .addValidationError(errorPath, error.message);
1669
1626
  });
1670
-
1671
- // Notify components to update
1672
- const stateEntry = getGlobalStore
1673
- .getState()
1674
- .stateComponents.get(stateKey);
1675
- if (stateEntry) {
1676
- stateEntry.components.forEach((component) => {
1677
- component.forceUpdate();
1678
- });
1679
- }
1627
+ notifyComponents(stateKey);
1680
1628
  }
1681
1629
 
1682
- // Call success/error callbacks
1683
- if (response?.success && sync.onSuccess) {
1630
+ if (response?.success && sync.onSuccess)
1684
1631
  sync.onSuccess(response.data);
1685
- } else if (!response?.success && sync.onError) {
1632
+ else if (!response?.success && sync.onError)
1686
1633
  sync.onError(response.error);
1687
- }
1688
1634
 
1689
1635
  return response;
1690
1636
  } catch (error) {
1691
- if (sync.onError) {
1692
- sync.onError(error);
1693
- }
1637
+ if (sync.onError) sync.onError(error);
1694
1638
  return { success: false, error };
1695
1639
  }
1696
1640
  };
1697
1641
  }
1698
1642
  if (prop === "_status") {
1699
- // Get current state at this path (non-reactive version)
1700
1643
  const thisReactiveState = getGlobalStore
1701
1644
  .getState()
1702
1645
  .getNestedState(stateKey, path);
1703
-
1704
- // Get initial state at this path
1705
1646
  const initialState =
1706
1647
  getGlobalStore.getState().initialStateGlobal[stateKey];
1707
1648
  const initialStateAtPath = getNestedValue(initialState, path);
1708
-
1709
- // Simply compare current state with initial state
1710
- if (isDeepEqual(thisReactiveState, initialStateAtPath)) {
1711
- return "fresh"; // Matches initial state
1712
- } else {
1713
- return "stale"; // Different from initial state
1714
- }
1649
+ return isDeepEqual(thisReactiveState, initialStateAtPath)
1650
+ ? "fresh"
1651
+ : "stale";
1715
1652
  }
1716
1653
  if (prop === "getStatus") {
1717
1654
  return function () {
1718
- // Get current state at this path (reactive version)
1719
1655
  const thisReactiveState = getGlobalStore().getNestedState(
1720
1656
  stateKey,
1721
1657
  path
1722
1658
  );
1723
-
1724
- // Get initial state at this path
1725
1659
  const initialState =
1726
1660
  getGlobalStore.getState().initialStateGlobal[stateKey];
1727
1661
  const initialStateAtPath = getNestedValue(initialState, path);
1728
- // Simply compare current state with initial state
1729
- if (isDeepEqual(thisReactiveState, initialStateAtPath)) {
1730
- return "fresh"; // Matches initial state
1731
- } else {
1732
- return "stale"; // Different from initial state
1733
- }
1662
+ return isDeepEqual(thisReactiveState, initialStateAtPath)
1663
+ ? "fresh"
1664
+ : "stale";
1734
1665
  };
1735
1666
  }
1736
1667
  if (prop === "removeStorage") {
@@ -1739,14 +1670,10 @@ function createProxyHandler<T>(
1739
1670
  getGlobalStore.getState().initialStateGlobal[stateKey];
1740
1671
  const initalOptionsGet = getInitialOptions(stateKey as string);
1741
1672
  const localKey = isFunction(initalOptionsGet?.localStorage?.key)
1742
- ? initalOptionsGet?.localStorage?.key(initialState)
1673
+ ? initalOptionsGet.localStorage.key(initialState)
1743
1674
  : initalOptionsGet?.localStorage?.key;
1744
-
1745
1675
  const storageKey = `${sessionId}-${stateKey}-${localKey}`;
1746
-
1747
- if (storageKey) {
1748
- localStorage.removeItem(storageKey);
1749
- }
1676
+ if (storageKey) localStorage.removeItem(storageKey);
1750
1677
  };
1751
1678
  }
1752
1679
  if (prop === "showValidationErrors") {
@@ -1754,49 +1681,30 @@ function createProxyHandler<T>(
1754
1681
  const init = getGlobalStore
1755
1682
  .getState()
1756
1683
  .getInitialOptions(stateKey)?.validation;
1757
-
1758
- if (!init?.key) {
1759
- throw new Error("Validation key not found");
1760
- }
1761
- const errors = getGlobalStore
1684
+ if (!init?.key) throw new Error("Validation key not found");
1685
+ return getGlobalStore
1762
1686
  .getState()
1763
1687
  .getValidationErrors(init.key + "." + path.join("."));
1764
-
1765
- return errors;
1766
1688
  };
1767
1689
  }
1768
1690
  if (Array.isArray(currentState)) {
1769
- const getSourceArrayAndIndices = (): {
1770
- item: any;
1771
- originalIndex: number;
1772
- }[] => {
1773
- // If meta exists, we're in a chain. Use the currentState and map it to its original index.
1774
- if (meta?.validIndices) {
1775
- return (currentState as any[]).map((item, index) => ({
1776
- item,
1777
- originalIndex: meta!.validIndices![index]!,
1778
- }));
1779
- }
1780
- // Otherwise, this is the first operation. Use the full array from the global store.
1781
- const sourceArray = getGlobalStore
1782
- .getState()
1783
- .getNestedState(stateKey, path) as any[];
1784
- return sourceArray.map((item, index) => ({
1785
- item,
1786
- originalIndex: index,
1787
- }));
1788
- };
1789
1691
  if (prop === "getSelected") {
1790
1692
  return () => {
1791
1693
  const selectedIndex = getGlobalStore
1792
1694
  .getState()
1793
1695
  .getSelectedIndex(stateKey, path.join("."));
1794
1696
  if (selectedIndex === undefined) return undefined;
1795
- return rebuildStateShape(
1796
- currentState[selectedIndex],
1797
- [...path, selectedIndex.toString()],
1798
- meta
1799
- );
1697
+
1698
+ const sourceArray = getGlobalStore
1699
+ .getState()
1700
+ .getNestedState(stateKey, path) as any[];
1701
+ if (!sourceArray || selectedIndex >= sourceArray.length)
1702
+ return undefined;
1703
+
1704
+ const selectedItem = sourceArray[selectedIndex];
1705
+ const itemId = `id:${selectedItem.id}`;
1706
+
1707
+ return rebuildStateShape(selectedItem, [...path, itemId], meta);
1800
1708
  };
1801
1709
  }
1802
1710
  if (prop === "clearSelected") {
@@ -1806,14 +1714,23 @@ function createProxyHandler<T>(
1806
1714
  }
1807
1715
  if (prop === "getSelectedIndex") {
1808
1716
  return () => {
1809
- const selectedIndex = getGlobalStore
1717
+ const globallySelectedIndex = getGlobalStore
1810
1718
  .getState()
1811
1719
  .getSelectedIndex(stateKey, path.join("."));
1720
+ if (globallySelectedIndex === undefined) return -1;
1721
+
1722
+ if (meta?.validIds) {
1723
+ const sourceIds = getOrderedIds(path) || [];
1724
+ const selectedItemId = sourceIds[globallySelectedIndex];
1725
+ if (!selectedItemId) return -1;
1726
+ const localIndex = meta.validIds.indexOf(selectedItemId);
1727
+ return localIndex;
1728
+ }
1812
1729
 
1813
- return selectedIndex ?? -1;
1730
+ return globallySelectedIndex;
1814
1731
  };
1815
1732
  }
1816
- // Complete fixed useVirtualView implementation
1733
+ // Replace the entire 'if (prop === "useVirtualView")' block with this
1817
1734
  if (prop === "useVirtualView") {
1818
1735
  return (
1819
1736
  options: VirtualViewOptions
@@ -1835,7 +1752,9 @@ function createProxyHandler<T>(
1835
1752
  const userHasScrolledAwayRef = useRef(false);
1836
1753
  const previousCountRef = useRef(0);
1837
1754
  const lastRangeRef = useRef(range);
1838
- // Subscribe to shadow state updates
1755
+ const orderedIds = getOrderedIds(path);
1756
+
1757
+ // Subscribe to shadow state updates for dynamic height changes
1839
1758
  useEffect(() => {
1840
1759
  const unsubscribe = getGlobalStore
1841
1760
  .getState()
@@ -1851,21 +1770,23 @@ function createProxyHandler<T>(
1851
1770
  ) as any[];
1852
1771
  const totalCount = sourceArray.length;
1853
1772
 
1854
- // Calculate heights and positions
1773
+ // Calculate total height and individual item positions
1855
1774
  const { totalHeight, positions } = useMemo(() => {
1856
- const rawShadowData = getGlobalStore
1857
- .getState()
1858
- .getShadowMetadata(stateKey, path);
1859
- const shadowArray: ItemMeta[] = Array.isArray(rawShadowData)
1860
- ? rawShadowData
1861
- : [];
1862
1775
  let height = 0;
1863
1776
  const pos: number[] = [];
1864
1777
  for (let i = 0; i < totalCount; i++) {
1865
1778
  pos[i] = height;
1866
- const measuredHeight =
1867
- shadowArray[i]?.virtualizer?.itemHeight;
1868
- height += measuredHeight || itemHeight;
1779
+ const itemId = orderedIds?.[i];
1780
+ if (itemId) {
1781
+ const itemPath = [...path, itemId];
1782
+ const itemMeta = getGlobalStore
1783
+ .getState()
1784
+ .getShadowMetadata(stateKey, itemPath);
1785
+ const measuredHeight = itemMeta?.virtualizer?.itemHeight;
1786
+ height += measuredHeight || itemHeight;
1787
+ } else {
1788
+ height += itemHeight;
1789
+ }
1869
1790
  }
1870
1791
  return { totalHeight: height, positions: pos };
1871
1792
  }, [
@@ -1874,87 +1795,65 @@ function createProxyHandler<T>(
1874
1795
  path.join("."),
1875
1796
  itemHeight,
1876
1797
  shadowUpdateTrigger,
1798
+ orderedIds,
1877
1799
  ]);
1878
1800
 
1879
- // Create virtual state
1801
+ // Create the virtual state object
1880
1802
  const virtualState = useMemo(() => {
1881
1803
  const start = Math.max(0, range.startIndex);
1882
1804
  const end = Math.min(totalCount, range.endIndex);
1883
- const validIndices = Array.from(
1884
- { length: end - start },
1885
- (_, i) => start + i
1886
- );
1887
- const slicedArray = validIndices.map((idx) => sourceArray[idx]);
1805
+ // The sliced array is the `currentState` for the new proxy
1806
+ const slicedArray = sourceArray.slice(start, end);
1807
+ // The `validIds` for the new proxy are the sliced IDs
1808
+ const slicedIds = orderedIds?.slice(start, end);
1809
+
1888
1810
  return rebuildStateShape(slicedArray as any, path, {
1889
1811
  ...meta,
1890
- validIndices,
1812
+ validIds: slicedIds,
1891
1813
  });
1892
- }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1814
+ }, [
1815
+ range.startIndex,
1816
+ range.endIndex,
1817
+ sourceArray,
1818
+ totalCount,
1819
+ orderedIds,
1820
+ ]);
1893
1821
 
1894
- // Helper to scroll to last item using stored ref
1895
1822
  const scrollToLastItem = useCallback(() => {
1896
- const rawShadowData = getGlobalStore
1897
- .getState()
1898
- .getShadowMetadata(stateKey, path);
1899
- const shadowArray: ItemMeta[] = Array.isArray(rawShadowData)
1900
- ? rawShadowData
1901
- : [];
1902
1823
  const lastIndex = totalCount - 1;
1903
-
1904
- if (lastIndex >= 0) {
1905
- const lastItemData = shadowArray[lastIndex];
1906
- if (lastItemData?.virtualizer?.domRef) {
1907
- const element = lastItemData.virtualizer.domRef;
1908
- if (element && element.scrollIntoView) {
1824
+ if (lastIndex >= 0 && orderedIds?.[lastIndex]) {
1825
+ const lastItemId = orderedIds[lastIndex];
1826
+ const lastItemPath = [...path, lastItemId];
1827
+ const lastItemMeta = getGlobalStore
1828
+ .getState()
1829
+ .getShadowMetadata(stateKey, lastItemPath);
1830
+ if (lastItemMeta?.virtualizer?.domRef) {
1831
+ const element = lastItemMeta.virtualizer.domRef;
1832
+ if (element?.scrollIntoView) {
1909
1833
  element.scrollIntoView({
1910
1834
  behavior: "auto",
1911
1835
  block: "end",
1912
- inline: "nearest",
1913
1836
  });
1914
1837
  return true;
1915
1838
  }
1916
1839
  }
1917
1840
  }
1918
1841
  return false;
1919
- }, [stateKey, path, totalCount]);
1842
+ }, [stateKey, path, totalCount, orderedIds]);
1920
1843
 
1921
- // Handle new items when at bottom
1922
1844
  useEffect(() => {
1923
1845
  if (!stickToBottom || totalCount === 0) return;
1924
-
1925
1846
  const hasNewItems = totalCount > previousCountRef.current;
1926
- const isInitialLoad =
1927
- previousCountRef.current === 0 && totalCount > 0;
1928
-
1929
- // Only auto-scroll if user hasn't scrolled away
1930
1847
  if (
1931
- (hasNewItems || isInitialLoad) &&
1848
+ hasNewItems &&
1932
1849
  wasAtBottomRef.current &&
1933
1850
  !userHasScrolledAwayRef.current
1934
1851
  ) {
1935
- const visibleCount = Math.ceil(
1936
- (containerRef.current?.clientHeight || 0) / itemHeight
1937
- );
1938
- const newRange = {
1939
- startIndex: Math.max(
1940
- 0,
1941
- totalCount - visibleCount - overscan
1942
- ),
1943
- endIndex: totalCount,
1944
- };
1945
-
1946
- setRange(newRange);
1947
-
1948
- const timeoutId = setTimeout(() => {
1949
- scrollToIndex(totalCount - 1, "smooth");
1950
- }, 50);
1951
- return () => clearTimeout(timeoutId);
1852
+ setTimeout(() => scrollToIndex(totalCount - 1, "smooth"), 50);
1952
1853
  }
1953
-
1954
1854
  previousCountRef.current = totalCount;
1955
- }, [totalCount, itemHeight, overscan]);
1855
+ }, [totalCount, stickToBottom]);
1956
1856
 
1957
- // Handle scroll events
1958
1857
  useEffect(() => {
1959
1858
  const container = containerRef.current;
1960
1859
  if (!container) return;
@@ -1963,21 +1862,12 @@ function createProxyHandler<T>(
1963
1862
  const { scrollTop, scrollHeight, clientHeight } = container;
1964
1863
  const distanceFromBottom =
1965
1864
  scrollHeight - scrollTop - clientHeight;
1966
-
1967
- // Track if we're at bottom
1968
1865
  wasAtBottomRef.current = distanceFromBottom < 5;
1969
-
1970
- // If user scrolls away from bottom past threshold, set flag
1971
- if (distanceFromBottom > 100) {
1866
+ if (distanceFromBottom > 100)
1972
1867
  userHasScrolledAwayRef.current = true;
1973
- }
1974
-
1975
- // If user scrolls back to bottom, cle
1976
- if (distanceFromBottom < 5) {
1868
+ if (distanceFromBottom < 5)
1977
1869
  userHasScrolledAwayRef.current = false;
1978
- }
1979
1870
 
1980
- // Update visible range based on scroll position
1981
1871
  let startIndex = 0;
1982
1872
  for (let i = 0; i < positions.length; i++) {
1983
1873
  if (positions[i]! > scrollTop - itemHeight * overscan) {
@@ -1985,7 +1875,6 @@ function createProxyHandler<T>(
1985
1875
  break;
1986
1876
  }
1987
1877
  }
1988
-
1989
1878
  let endIndex = startIndex;
1990
1879
  const viewportEnd = scrollTop + clientHeight;
1991
1880
  for (let i = startIndex; i < positions.length; i++) {
@@ -1994,14 +1883,12 @@ function createProxyHandler<T>(
1994
1883
  }
1995
1884
  endIndex = i;
1996
1885
  }
1997
-
1998
1886
  const newStartIndex = Math.max(0, startIndex);
1999
1887
  const newEndIndex = Math.min(
2000
1888
  totalCount,
2001
1889
  endIndex + 1 + overscan
2002
1890
  );
2003
1891
 
2004
- // THE FIX: Only update state if the visible range of items has changed.
2005
1892
  if (
2006
1893
  newStartIndex !== lastRangeRef.current.startIndex ||
2007
1894
  newEndIndex !== lastRangeRef.current.endIndex
@@ -2020,81 +1907,30 @@ function createProxyHandler<T>(
2020
1907
  container.addEventListener("scroll", handleScroll, {
2021
1908
  passive: true,
2022
1909
  });
2023
-
2024
- // Only auto-scroll on initial load when user hasn't scrolled away
2025
- if (
2026
- stickToBottom &&
2027
- totalCount > 0 &&
2028
- !userHasScrolledAwayRef.current
2029
- ) {
2030
- const { scrollTop } = container;
2031
- // Only if we're at the very top (initial load)
2032
- if (scrollTop === 0) {
2033
- container.scrollTop = container.scrollHeight;
2034
- wasAtBottomRef.current = true;
2035
- }
2036
- }
2037
-
2038
- handleScroll();
2039
-
2040
- return () => {
1910
+ handleScroll(); // Initial check
1911
+ return () =>
2041
1912
  container.removeEventListener("scroll", handleScroll);
2042
- };
2043
1913
  }, [positions, totalCount, itemHeight, overscan, stickToBottom]);
2044
1914
 
2045
1915
  const scrollToBottom = useCallback(() => {
2046
1916
  wasAtBottomRef.current = true;
2047
1917
  userHasScrolledAwayRef.current = false;
2048
- const scrolled = scrollToLastItem();
2049
- if (!scrolled && containerRef.current) {
1918
+ if (!scrollToLastItem() && containerRef.current) {
2050
1919
  containerRef.current.scrollTop =
2051
1920
  containerRef.current.scrollHeight;
2052
1921
  }
2053
1922
  }, [scrollToLastItem]);
1923
+
2054
1924
  const scrollToIndex = useCallback(
2055
1925
  (index: number, behavior: ScrollBehavior = "smooth") => {
2056
1926
  const container = containerRef.current;
2057
1927
  if (!container) return;
2058
-
2059
- const isLastItem = index === totalCount - 1;
2060
-
2061
- // --- Special Case: The Last Item ---
2062
- if (isLastItem) {
2063
- // For the last item, scrollIntoView can fail. The most reliable method
2064
- // is to scroll the parent container to its maximum scroll height.
2065
- container.scrollTo({
2066
- top: container.scrollHeight,
2067
- behavior: behavior,
2068
- });
2069
- return; // We're done.
2070
- }
2071
-
2072
- // --- Standard Case: All Other Items ---
2073
- // For all other items, we find the ref and use scrollIntoView.
2074
- const rawShadowData = getGlobalStore
2075
- .getState()
2076
- .getShadowMetadata(stateKey, path);
2077
- const shadowArray: ItemMeta[] = Array.isArray(rawShadowData)
2078
- ? rawShadowData
2079
- : [];
2080
- const itemData = shadowArray[index];
2081
- const element = itemData?.virtualizer?.domRef;
2082
-
2083
- if (element) {
2084
- // 'center' gives a better user experience for items in the middle of the list.
2085
- element.scrollIntoView({
2086
- behavior: behavior,
2087
- block: "center",
2088
- });
2089
- } else if (positions[index] !== undefined) {
2090
- // Fallback if the ref isn't available for some reason.
2091
- container.scrollTo({
2092
- top: positions[index],
2093
- behavior,
2094
- });
1928
+ const top = positions[index];
1929
+ if (top !== undefined) {
1930
+ container.scrollTo({ top, behavior });
2095
1931
  }
2096
1932
  },
2097
- [positions, stateKey, path, totalCount] // Add totalCount to the dependencies
1933
+ [positions]
2098
1934
  );
2099
1935
 
2100
1936
  const virtualizerProps = {
@@ -2123,106 +1959,112 @@ function createProxyHandler<T>(
2123
1959
  };
2124
1960
  };
2125
1961
  }
2126
- if (prop === "stateMapNoRender") {
1962
+ if (prop === "stateMap") {
2127
1963
  return (
2128
1964
  callbackfn: (
2129
- value: InferArrayElement<T>,
2130
- setter: StateObject<InferArrayElement<T>>,
1965
+ value: any,
1966
+ setter: any,
2131
1967
  index: number,
2132
- array: T,
2133
- arraySetter: StateObject<T>
2134
- ) => any
1968
+ array: any,
1969
+ arraySetter: any
1970
+ ) => void
2135
1971
  ) => {
2136
1972
  const arrayToMap = currentState as any[];
2137
- return arrayToMap.map((item, index) => {
2138
- let originalIndex: number;
2139
- // We READ from the meta object using the CORRECT property name: `validIndices`.
2140
- if (
2141
- meta?.validIndices &&
2142
- meta.validIndices[index] !== undefined
2143
- ) {
2144
- originalIndex = meta.validIndices[index]!;
2145
- } else {
2146
- originalIndex = index;
2147
- }
2148
- const finalPath = [...path, originalIndex.toString()];
1973
+ const itemIdsForCurrentArray =
1974
+ meta?.validIds || getOrderedIds(path) || [];
1975
+ const arraySetter = rebuildStateShape(currentState, path, meta);
2149
1976
 
2150
- const setter = rebuildStateShape(item, finalPath, meta); // Pass meta through
1977
+ return arrayToMap.map((item, index) => {
1978
+ const itemId = itemIdsForCurrentArray[index] || `id:${item.id}`;
1979
+ const itemPath = [...path, itemId];
1980
+ const itemSetter = rebuildStateShape(item, itemPath, meta);
2151
1981
  return callbackfn(
2152
1982
  item,
2153
- setter,
1983
+ itemSetter,
2154
1984
  index,
2155
- currentState as any,
2156
- rebuildStateShape(currentState as any, path, meta)
1985
+ currentState,
1986
+ arraySetter
2157
1987
  );
2158
1988
  });
2159
1989
  };
2160
1990
  }
2161
- if (prop === "$stateMap") {
1991
+ if (prop === "stateMapNoRender") {
2162
1992
  return (
2163
1993
  callbackfn: (
2164
- value: InferArrayElement<T>,
2165
- setter: StateObject<InferArrayElement<T>>,
1994
+ value: any,
1995
+ setter: any,
2166
1996
  index: number,
2167
- array: T,
2168
- arraySetter: StateObject<T>
1997
+ array: any,
1998
+ arraySetter: any
2169
1999
  ) => void
2170
2000
  ) => {
2171
- return createElement(SignalMapRenderer, {
2172
- proxy: {
2173
- _stateKey: stateKey,
2174
- _path: path,
2175
- _mapFn: callbackfn as any, // Pass the actual function, not string
2176
- },
2001
+ const arrayToMap = currentState as any[];
2002
+ const itemIdsForCurrentArray =
2003
+ meta?.validIds || getOrderedIds(path) || [];
2004
+ const arraySetter = rebuildStateShape(currentState, path, meta);
2177
2005
 
2178
- rebuildStateShape,
2006
+ return arrayToMap.map((item, index) => {
2007
+ const itemId = itemIdsForCurrentArray[index] || `id:${item.id}`;
2008
+ const finalPath = [...path, itemId];
2009
+ const setter = rebuildStateShape(item, finalPath, meta);
2010
+ return callbackfn(
2011
+ item,
2012
+ setter,
2013
+ index,
2014
+ currentState,
2015
+ arraySetter
2016
+ );
2179
2017
  });
2180
2018
  };
2181
2019
  }
2020
+ if (prop === "$stateMap") {
2021
+ return (callbackfn: any) =>
2022
+ createElement(SignalMapRenderer, {
2023
+ proxy: { _stateKey: stateKey, _path: path, _mapFn: callbackfn },
2024
+ rebuildStateShape,
2025
+ });
2026
+ }
2182
2027
  if (prop === "stateList") {
2183
2028
  return (
2184
2029
  callbackfn: (
2185
- value: InferArrayElement<T>,
2186
- setter: StateObject<InferArrayElement<T>>,
2030
+ value: any,
2031
+ setter: any,
2187
2032
  index: { localIndex: number; originalIndex: number },
2188
- array: T,
2189
- arraySetter: StateObject<T>
2190
- ) => any,
2191
- formOpts?: FormOptsType
2033
+ array: any,
2034
+ arraySetter: any
2035
+ ) => ReactNode
2192
2036
  ) => {
2193
- const arrayToMap = getGlobalStore
2194
- .getState()
2195
- .getNestedState(stateKey, path) as any[];
2196
-
2197
- if (!Array.isArray(arrayToMap)) {
2198
- console.warn(
2199
- `stateList called on a non-array value at path: ${path.join(".")}.`
2200
- );
2201
- return null;
2202
- }
2203
-
2204
- const indicesToMap =
2205
- meta?.validIndices ||
2206
- Array.from({ length: arrayToMap.length }, (_, i) => i);
2037
+ const arrayToMap = currentState as any[];
2038
+ if (!Array.isArray(arrayToMap)) return null;
2039
+
2040
+ const itemIdsForCurrentArray =
2041
+ meta?.validIds || getOrderedIds(path) || [];
2042
+ const sourceIds = getOrderedIds(path) || [];
2043
+ const arraySetter = rebuildStateShape(
2044
+ arrayToMap as any,
2045
+ path,
2046
+ meta
2047
+ );
2207
2048
 
2208
- return indicesToMap.map((originalIndex, localIndex) => {
2209
- const item = arrayToMap[originalIndex];
2210
- const finalPath = [...path, originalIndex.toString()];
2049
+ return arrayToMap.map((item, localIndex) => {
2050
+ const itemId =
2051
+ itemIdsForCurrentArray[localIndex] || `id:${item.id}`;
2052
+ const originalIndex = sourceIds.indexOf(itemId);
2053
+ const finalPath = [...path, itemId];
2211
2054
  const setter = rebuildStateShape(item, finalPath, meta);
2212
- const itemComponentId = `${componentId}-${path.join(".")}-${originalIndex}`;
2055
+ const itemComponentId = `${componentId}-${path.join(".")}-${itemId}`;
2213
2056
 
2214
2057
  return createElement(CogsItemWrapper, {
2215
- key: originalIndex,
2058
+ key: itemId,
2216
2059
  stateKey,
2217
2060
  itemComponentId,
2218
- formOpts,
2219
2061
  itemPath: finalPath,
2220
2062
  children: callbackfn(
2221
2063
  item,
2222
2064
  setter,
2223
2065
  { localIndex, originalIndex },
2224
2066
  arrayToMap as any,
2225
- rebuildStateShape(arrayToMap as any, path, meta)
2067
+ arraySetter
2226
2068
  ),
2227
2069
  });
2228
2070
  });
@@ -2243,15 +2085,30 @@ function createProxyHandler<T>(
2243
2085
  );
2244
2086
  };
2245
2087
  }
2246
-
2247
2088
  if (prop === "index") {
2248
2089
  return (index: number) => {
2249
- const indexValue = currentState[index];
2250
- return rebuildStateShape(indexValue, [...path, index.toString()]);
2090
+ const idList = meta?.validIds || getOrderedIds(path);
2091
+ const itemId = idList?.[index];
2092
+
2093
+ if (!itemId) {
2094
+ return rebuildStateShape(undefined as T, [
2095
+ ...path,
2096
+ index.toString(),
2097
+ ]);
2098
+ }
2099
+
2100
+ const sourceArray = getGlobalStore
2101
+ .getState()
2102
+ .getNestedState(stateKey, path) as any[];
2103
+ const itemData = sourceArray.find(
2104
+ (item) => `id:${item.id}` === itemId
2105
+ );
2106
+
2107
+ const itemPath = [...path, itemId];
2108
+ return rebuildStateShape(itemData, itemPath, meta);
2251
2109
  };
2252
2110
  }
2253
2111
  if (prop === "last") {
2254
- // Added handler for 'last'
2255
2112
  return () => {
2256
2113
  const currentArray = getGlobalStore
2257
2114
  .getState()
@@ -2260,14 +2117,11 @@ function createProxyHandler<T>(
2260
2117
  const lastIndex = currentArray.length - 1;
2261
2118
  const lastValue = currentArray[lastIndex];
2262
2119
  const newPath = [...path, lastIndex.toString()];
2263
- // shapeCache.clear(); // Decide if you need cache invalidation for 'last' access
2264
- // stateVersion++;
2265
2120
  return rebuildStateShape(lastValue, newPath);
2266
2121
  };
2267
2122
  }
2268
2123
  if (prop === "insert") {
2269
2124
  return (payload: UpdateArg<T>) => {
2270
- // ADDED: Invalidate cache on insert
2271
2125
  invalidateCachePath(path);
2272
2126
  pushFunc(effectiveSetState, payload, path, stateKey);
2273
2127
  return rebuildStateShape(
@@ -2276,7 +2130,6 @@ function createProxyHandler<T>(
2276
2130
  );
2277
2131
  };
2278
2132
  }
2279
-
2280
2133
  if (prop === "uniqueInsert") {
2281
2134
  return (
2282
2135
  payload: UpdateArg<T>,
@@ -2292,19 +2145,12 @@ function createProxyHandler<T>(
2292
2145
 
2293
2146
  let matchedItem: any = null;
2294
2147
  const isUnique = !currentArray.some((item) => {
2295
- if (fields) {
2296
- const isMatch = fields.every((field) =>
2297
- isDeepEqual(item[field], newValue[field])
2298
- );
2299
- if (isMatch) {
2300
- matchedItem = item;
2301
- }
2302
- return isMatch;
2303
- }
2304
- const isMatch = isDeepEqual(item, newValue);
2305
- if (isMatch) {
2306
- matchedItem = item;
2307
- }
2148
+ const isMatch = fields
2149
+ ? fields.every((field) =>
2150
+ isDeepEqual(item[field], newValue[field])
2151
+ )
2152
+ : isDeepEqual(item, newValue);
2153
+ if (isMatch) matchedItem = item;
2308
2154
  return isMatch;
2309
2155
  });
2310
2156
 
@@ -2321,11 +2167,9 @@ function createProxyHandler<T>(
2321
2167
  }
2322
2168
  };
2323
2169
  }
2324
-
2325
2170
  if (prop === "cut") {
2326
2171
  return (index: number, options?: { waitForSync?: boolean }) => {
2327
2172
  if (options?.waitForSync) return;
2328
- // ADDED: Invalidate cache on cut
2329
2173
  invalidateCachePath(path);
2330
2174
  cutFunc(effectiveSetState, path, stateKey, index);
2331
2175
  return rebuildStateShape(
@@ -2336,51 +2180,70 @@ function createProxyHandler<T>(
2336
2180
  }
2337
2181
  if (prop === "cutByValue") {
2338
2182
  return (value: string | number | boolean) => {
2339
- for (let index = 0; index < currentState.length; index++) {
2340
- if (currentState[index] === value) {
2341
- cutFunc(effectiveSetState, path, stateKey, index);
2342
- }
2343
- }
2183
+ const index = currentState.findIndex((item) => item === value);
2184
+ if (index > -1) cutFunc(effectiveSetState, path, stateKey, index);
2344
2185
  };
2345
2186
  }
2346
2187
  if (prop === "toggleByValue") {
2347
2188
  return (value: string | number | boolean) => {
2348
2189
  const index = currentState.findIndex((item) => item === value);
2349
2190
  if (index > -1) {
2350
- // Value exists, so cut it
2351
2191
  cutFunc(effectiveSetState, path, stateKey, index);
2352
2192
  } else {
2353
- // Value doesn't exist, so insert it
2354
2193
  pushFunc(effectiveSetState, value as any, path, stateKey);
2355
2194
  }
2356
2195
  };
2357
2196
  }
2358
- if (prop === "stateFind") {
2359
- return (
2360
- callbackfn: (
2361
- value: InferArrayElement<T>,
2362
- index: number
2363
- ) => boolean
2364
- ) => {
2365
- const sourceWithIndices = getSourceArrayAndIndices();
2366
- const found = sourceWithIndices.find(({ item }, index) =>
2367
- callbackfn(item, index)
2197
+ if (prop === "stateFilter") {
2198
+ return (callbackfn: (value: any, index: number) => boolean) => {
2199
+ const sourceIds = meta?.validIds || getOrderedIds(path) || [];
2200
+ const sourceArray = getGlobalStore
2201
+ .getState()
2202
+ .getNestedState(stateKey, path) as any[];
2203
+ const sourceMap = new Map(
2204
+ sourceArray.map((item) => [`id:${item.id}`, item])
2368
2205
  );
2369
- if (!found) return undefined;
2370
- const finalPath = [...path, found.originalIndex.toString()];
2371
- return rebuildStateShape(found.item, finalPath, meta);
2206
+
2207
+ const newValidIds: string[] = [];
2208
+ const newFilteredArray: any[] = [];
2209
+
2210
+ sourceIds.forEach((id, index) => {
2211
+ const item = sourceMap.get(id);
2212
+ if (item && callbackfn(item, index)) {
2213
+ newValidIds.push(id);
2214
+ newFilteredArray.push(item);
2215
+ }
2216
+ });
2217
+
2218
+ return rebuildStateShape(newFilteredArray as any, path, {
2219
+ validIds: newValidIds,
2220
+ });
2221
+ };
2222
+ }
2223
+ if (prop === "stateSort") {
2224
+ return (compareFn: (a: any, b: any) => number) => {
2225
+ const sourceArray = currentState as any[];
2226
+ const itemsWithIds = sourceArray.map((item) => ({
2227
+ item,
2228
+ id: `id:${item.id}`,
2229
+ }));
2230
+ itemsWithIds.sort((a, b) => compareFn(a.item, b.item));
2231
+ const sortedArray = itemsWithIds.map((d) => d.item);
2232
+ const newValidIds = itemsWithIds.map((d) => d.id);
2233
+ return rebuildStateShape(sortedArray as any, path, {
2234
+ validIds: newValidIds,
2235
+ });
2372
2236
  };
2373
2237
  }
2374
-
2375
2238
  if (prop === "findWith") {
2376
2239
  return (thisKey: keyof InferArrayElement<T>, thisValue: any) => {
2377
- const sourceWithIndices = getSourceArrayAndIndices();
2378
- const found = sourceWithIndices.find(
2379
- ({ item }) => item[thisKey] === thisValue
2240
+ const foundItem = (currentState as any[]).find(
2241
+ (item) => item[thisKey] === thisValue
2380
2242
  );
2381
- if (!found) return undefined;
2382
- const finalPath = [...path, found.originalIndex.toString()];
2383
- return rebuildStateShape(found.item, finalPath, meta);
2243
+ if (!foundItem) return undefined;
2244
+ const itemId = `id:${foundItem.id}`;
2245
+ const finalPath = [...path, itemId];
2246
+ return rebuildStateShape(foundItem, finalPath, meta);
2384
2247
  };
2385
2248
  }
2386
2249
  }
@@ -2390,7 +2253,6 @@ function createProxyHandler<T>(
2390
2253
  const parentValue = getGlobalStore
2391
2254
  .getState()
2392
2255
  .getNestedState(stateKey, parentPath);
2393
-
2394
2256
  if (Array.isArray(parentValue) && prop === "cut") {
2395
2257
  return () =>
2396
2258
  cutFunc(
@@ -2401,16 +2263,28 @@ function createProxyHandler<T>(
2401
2263
  );
2402
2264
  }
2403
2265
  }
2404
-
2405
2266
  if (prop === "get") {
2406
2267
  return () => {
2407
- if (meta?.validIndices && Array.isArray(currentState)) {
2408
- // For filtered arrays, return only the items at validIndices
2409
- const fullArray = getGlobalStore
2268
+ // Check if this proxy represents a derived array.
2269
+ // A derived array proxy has `meta.validIds` AND its `currentState` is an array.
2270
+ if (meta?.validIds && Array.isArray(currentState)) {
2271
+ // It IS a derived array proxy. Reconstruct it to ensure freshness.
2272
+ const sourceArray = getGlobalStore
2410
2273
  .getState()
2411
2274
  .getNestedState(stateKey, path) as any[];
2412
- return meta.validIndices.map((index) => fullArray[index]);
2275
+ if (!Array.isArray(sourceArray)) return [];
2276
+
2277
+ const sourceMap = new Map(
2278
+ sourceArray.map((item: any) => [`id:${item.id}`, item])
2279
+ );
2280
+
2281
+ return meta.validIds
2282
+ .map((id) => sourceMap.get(id))
2283
+ .filter(Boolean);
2413
2284
  }
2285
+
2286
+ // For all other cases (non-derived arrays, single items, properties),
2287
+ // the standard lookup is correct.
2414
2288
  return getGlobalStore.getState().getNestedState(stateKey, path);
2415
2289
  };
2416
2290
  }
@@ -2422,19 +2296,13 @@ function createProxyHandler<T>(
2422
2296
  _effect: fn.toString(),
2423
2297
  });
2424
2298
  }
2425
-
2426
2299
  if (prop === "$get") {
2427
- return () =>
2428
- $cogsSignal({
2429
- _stateKey: stateKey,
2430
- _path: path,
2431
- });
2300
+ return () => $cogsSignal({ _stateKey: stateKey, _path: path });
2432
2301
  }
2433
2302
  if (prop === "lastSynced") {
2434
2303
  const syncKey = `${stateKey}:${path.join(".")}`;
2435
2304
  return getGlobalStore.getState().getSyncInfo(syncKey);
2436
2305
  }
2437
-
2438
2306
  if (prop == "getLocalStorage") {
2439
2307
  return (key: string) =>
2440
2308
  loadFromLocalStorage(sessionId + "-" + stateKey + "-" + key);
@@ -2442,13 +2310,16 @@ function createProxyHandler<T>(
2442
2310
  if (prop === "_selected") {
2443
2311
  const parentPath = path.slice(0, -1);
2444
2312
  const parentKey = parentPath.join(".");
2445
- const parent = getGlobalStore
2446
- .getState()
2447
- .getNestedState(stateKey, parentPath);
2448
- if (Array.isArray(parent)) {
2449
- const currentIndex = Number(path[path.length - 1]);
2313
+ if (
2314
+ Array.isArray(
2315
+ getGlobalStore.getState().getNestedState(stateKey, parentPath)
2316
+ )
2317
+ ) {
2318
+ const itemId = path[path.length - 1];
2319
+ const orderedIds = getOrderedIds(parentPath);
2320
+ const thisIndex = orderedIds?.indexOf(itemId!);
2450
2321
  return (
2451
- currentIndex ===
2322
+ thisIndex ===
2452
2323
  getGlobalStore.getState().getSelectedIndex(stateKey, parentKey)
2453
2324
  );
2454
2325
  }
@@ -2457,37 +2328,39 @@ function createProxyHandler<T>(
2457
2328
  if (prop === "setSelected") {
2458
2329
  return (value: boolean) => {
2459
2330
  const parentPath = path.slice(0, -1);
2460
- const thisIndex = Number(path[path.length - 1]);
2461
- const parentKey = parentPath.join(".");
2331
+ const itemId = path[path.length - 1];
2332
+ const orderedIds = getOrderedIds(parentPath);
2333
+ const thisIndex = orderedIds?.indexOf(itemId!);
2462
2334
 
2463
- if (value) {
2464
- getGlobalStore
2465
- .getState()
2466
- .setSelectedIndex(stateKey, parentKey, thisIndex);
2467
- } else {
2468
- getGlobalStore
2469
- .getState()
2470
- .setSelectedIndex(stateKey, parentKey, undefined);
2471
- }
2335
+ if (thisIndex === undefined || thisIndex === -1) return;
2472
2336
 
2337
+ const parentKey = parentPath.join(".");
2338
+ getGlobalStore
2339
+ .getState()
2340
+ .setSelectedIndex(
2341
+ stateKey,
2342
+ parentKey,
2343
+ value ? thisIndex : undefined
2344
+ );
2473
2345
  const nested = getGlobalStore
2474
2346
  .getState()
2475
2347
  .getNestedState(stateKey, [...parentPath]);
2476
2348
  updateFn(effectiveSetState, nested, parentPath);
2477
-
2478
- // Invalidate cache for this path
2479
2349
  invalidateCachePath(parentPath);
2480
2350
  };
2481
2351
  }
2482
2352
  if (prop === "toggleSelected") {
2483
2353
  return () => {
2484
2354
  const parentPath = path.slice(0, -1);
2485
- const thisIndex = Number(path[path.length - 1]);
2355
+ const itemId = path[path.length - 1];
2356
+ const orderedIds = getOrderedIds(parentPath);
2357
+ const thisIndex = orderedIds?.indexOf(itemId!);
2358
+ if (thisIndex === undefined || thisIndex === -1) return;
2359
+
2486
2360
  const parentKey = parentPath.join(".");
2487
2361
  const selectedIndex = getGlobalStore
2488
2362
  .getState()
2489
2363
  .getSelectedIndex(stateKey, parentKey);
2490
-
2491
2364
  getGlobalStore
2492
2365
  .getState()
2493
2366
  .setSelectedIndex(
@@ -2495,11 +2368,11 @@ function createProxyHandler<T>(
2495
2368
  parentKey,
2496
2369
  selectedIndex === thisIndex ? undefined : thisIndex
2497
2370
  );
2371
+
2498
2372
  const nested = getGlobalStore
2499
2373
  .getState()
2500
2374
  .getNestedState(stateKey, [...parentPath]);
2501
2375
  updateFn(effectiveSetState, nested, parentPath);
2502
-
2503
2376
  invalidateCachePath(parentPath);
2504
2377
  };
2505
2378
  }
@@ -2509,34 +2382,20 @@ function createProxyHandler<T>(
2509
2382
  const init = getGlobalStore
2510
2383
  .getState()
2511
2384
  .getInitialOptions(stateKey)?.validation;
2512
-
2513
- if (!init?.key) {
2514
- throw new Error("Validation key not found");
2515
- }
2516
-
2517
- // Clear existing errors for this validation key
2385
+ if (!init?.key) throw new Error("Validation key not found");
2518
2386
  removeValidationError(init.key);
2519
- console.log("addValidationError", errors);
2520
- // Add each new error
2521
2387
  errors.forEach((error) => {
2522
2388
  const fullErrorPath = [init.key, ...error.path].join(".");
2523
- console.log("fullErrorPath", fullErrorPath);
2524
2389
  addValidationError(fullErrorPath, error.message);
2525
2390
  });
2526
-
2527
- // Notify components to update
2528
2391
  notifyComponents(stateKey);
2529
2392
  };
2530
2393
  }
2531
2394
  if (prop === "applyJsonPatch") {
2532
2395
  return (patches: any[]) => {
2533
- // This part is correct.
2534
2396
  const currentState =
2535
2397
  getGlobalStore.getState().cogsStateStore[stateKey];
2536
- const patchResult = applyPatch(currentState, patches);
2537
- const newState = patchResult.newDocument;
2538
-
2539
- // This is also correct.
2398
+ const newState = applyPatch(currentState, patches).newDocument;
2540
2399
  updateGlobalState(
2541
2400
  stateKey,
2542
2401
  getGlobalStore.getState().initialStateGlobal[stateKey],
@@ -2545,105 +2404,7 @@ function createProxyHandler<T>(
2545
2404
  componentId,
2546
2405
  sessionId
2547
2406
  );
2548
-
2549
- const stateEntry = getGlobalStore
2550
- .getState()
2551
- .stateComponents.get(stateKey); // Use stateKey here
2552
-
2553
- if (stateEntry) {
2554
- // Use `getDifferences` between the state before and after the patch.
2555
- const changedPaths = getDifferences(currentState, newState);
2556
- const changedPathsSet = new Set(changedPaths);
2557
-
2558
- // There is no single `primaryPathToCheck` for a patch, so we just check against the full set.
2559
-
2560
- for (const [
2561
- componentKey,
2562
- component,
2563
- ] of stateEntry.components.entries()) {
2564
- let shouldUpdate = false;
2565
- const reactiveTypes = Array.isArray(component.reactiveType)
2566
- ? component.reactiveType
2567
- : [component.reactiveType || "component"];
2568
-
2569
- if (reactiveTypes.includes("none")) continue;
2570
- if (reactiveTypes.includes("all")) {
2571
- component.forceUpdate();
2572
- continue;
2573
- }
2574
-
2575
- if (reactiveTypes.includes("component")) {
2576
- // This is the core logic that needs to be copied.
2577
- // Check if any of the component's watched paths are in the set of changed paths.
2578
- if (component.paths.has("")) {
2579
- // Always update for root listeners
2580
- shouldUpdate = true;
2581
- }
2582
-
2583
- if (!shouldUpdate) {
2584
- for (const changedPath of changedPathsSet) {
2585
- // Direct match first (fastest)
2586
- if (component.paths.has(changedPath)) {
2587
- shouldUpdate = true;
2588
- break;
2589
- }
2590
-
2591
- // Check parent paths more efficiently
2592
- let dotIndex = changedPath.lastIndexOf(".");
2593
- while (dotIndex !== -1) {
2594
- const parentPath = changedPath.substring(0, dotIndex);
2595
- if (component.paths.has(parentPath)) {
2596
- shouldUpdate = true;
2597
- break;
2598
- }
2599
- // Skip numeric segments more efficiently
2600
- const lastSegment = changedPath.substring(
2601
- dotIndex + 1
2602
- );
2603
- if (!isNaN(Number(lastSegment))) {
2604
- // For array indices, check the parent collection path
2605
- const parentDotIndex = parentPath.lastIndexOf(".");
2606
- if (parentDotIndex !== -1) {
2607
- const grandParentPath = parentPath.substring(
2608
- 0,
2609
- parentDotIndex
2610
- );
2611
- if (component.paths.has(grandParentPath)) {
2612
- shouldUpdate = true;
2613
- break;
2614
- }
2615
- }
2616
- }
2617
- dotIndex = parentPath.lastIndexOf(".");
2618
- }
2619
-
2620
- if (shouldUpdate) break;
2621
- }
2622
- }
2623
- }
2624
-
2625
- if (!shouldUpdate && reactiveTypes.includes("deps")) {
2626
- // Use `newState` (the result of the patch) for dependency checks.
2627
- if (component.depsFunction) {
2628
- const depsResult = component.depsFunction(newState);
2629
- let depsChanged = false;
2630
- if (typeof depsResult === "boolean") {
2631
- if (depsResult) depsChanged = true;
2632
- } else if (!isDeepEqual(component.deps, depsResult)) {
2633
- component.deps = depsResult;
2634
- depsChanged = true;
2635
- }
2636
- if (depsChanged) {
2637
- shouldUpdate = true;
2638
- }
2639
- }
2640
- }
2641
-
2642
- if (shouldUpdate) {
2643
- component.forceUpdate();
2644
- }
2645
- }
2646
- }
2407
+ notifyComponents(stateKey); // Simplified notification
2647
2408
  };
2648
2409
  }
2649
2410
  if (prop === "validateZodSchema") {
@@ -2651,75 +2412,31 @@ function createProxyHandler<T>(
2651
2412
  const init = getGlobalStore
2652
2413
  .getState()
2653
2414
  .getInitialOptions(stateKey)?.validation;
2654
- const addValidationError =
2655
- getGlobalStore.getState().addValidationError;
2656
-
2657
- if (!init?.zodSchema) {
2658
- throw new Error("Zod schema not found");
2659
- }
2415
+ if (!init?.zodSchema || !init?.key)
2416
+ throw new Error("Zod schema or validation key not found");
2660
2417
 
2661
- if (!init?.key) {
2662
- throw new Error("Validation key not found");
2663
- }
2664
2418
  removeValidationError(init.key);
2665
2419
  const thisObject =
2666
2420
  getGlobalStore.getState().cogsStateStore[stateKey];
2421
+ const result = init.zodSchema.safeParse(thisObject);
2667
2422
 
2668
- try {
2669
- // First clear any existing validation errors for this schema
2670
- // This ensures we don't have stale errors
2671
- const existingErrors = getGlobalStore
2672
- .getState()
2673
- .getValidationErrors(init.key);
2674
- if (existingErrors && existingErrors.length > 0) {
2675
- existingErrors.forEach(([errorPath]) => {
2676
- if (errorPath && errorPath.startsWith(init.key!)) {
2677
- removeValidationError(errorPath);
2678
- }
2679
- });
2680
- }
2681
-
2682
- // Attempt to validate with Zod
2683
- const result = init.zodSchema.safeParse(thisObject);
2684
-
2685
- if (!result.success) {
2686
- // Process Zod errors and add them to the validation store
2687
- const zodErrors = result.error.errors;
2688
-
2689
- zodErrors.forEach((error) => {
2690
- const errorPath = error.path;
2691
- const errorMessage = error.message;
2692
-
2693
- // Build the full path for the validation error
2694
- // Format: validationKey.path.to.field
2695
- const fullErrorPath = [init.key, ...errorPath].join(".");
2696
-
2697
- // Add the error to the store
2698
- addValidationError(fullErrorPath, errorMessage);
2699
- });
2700
-
2701
- notifyComponents(stateKey);
2702
-
2703
- return false;
2704
- }
2705
-
2706
- return true;
2707
- } catch (error) {
2708
- console.error("Zod schema validation failed", error);
2423
+ if (!result.success) {
2424
+ result.error.errors.forEach((error) => {
2425
+ const fullErrorPath = [init.key, ...error.path].join(".");
2426
+ addValidationError(fullErrorPath, error.message);
2427
+ });
2428
+ notifyComponents(stateKey);
2709
2429
  return false;
2710
2430
  }
2431
+ return true;
2711
2432
  };
2712
2433
  }
2713
2434
  if (prop === "_componentId") return componentId;
2714
- if (prop === "getComponents") {
2435
+ if (prop === "getComponents")
2715
2436
  return () => getGlobalStore().stateComponents.get(stateKey);
2716
- }
2717
- if (prop === "getAllFormRefs") {
2718
- return () => {
2719
- return formRefStore.getState().getFormRefsByStateKey(stateKey);
2720
- };
2721
- }
2722
-
2437
+ if (prop === "getAllFormRefs")
2438
+ return () =>
2439
+ formRefStore.getState().getFormRefsByStateKey(stateKey);
2723
2440
  if (prop === "_initialState")
2724
2441
  return getGlobalStore.getState().initialStateGlobal[stateKey];
2725
2442
  if (prop === "_serverState")
@@ -2732,13 +2449,9 @@ function createProxyHandler<T>(
2732
2449
  if (prop === "removeValidation") return baseObj.removeValidation;
2733
2450
  }
2734
2451
  if (prop === "getFormRef") {
2735
- return () => {
2736
- return formRefStore
2737
- .getState()
2738
- .getFormRef(stateKey + "." + path.join("."));
2739
- };
2452
+ return () =>
2453
+ formRefStore.getState().getFormRef(stateKey + "." + path.join("."));
2740
2454
  }
2741
-
2742
2455
  if (prop === "validationWrapper") {
2743
2456
  return ({
2744
2457
  children,
@@ -2753,20 +2466,16 @@ function createProxyHandler<T>(
2753
2466
  }
2754
2467
  path={path}
2755
2468
  stateKey={stateKey}
2756
- validIndices={meta?.validIndices}
2757
2469
  >
2758
2470
  {children}
2759
2471
  </ValidationWrapper>
2760
2472
  );
2761
2473
  }
2762
-
2763
2474
  if (prop === "_stateKey") return stateKey;
2764
2475
  if (prop === "_path") return path;
2765
2476
  if (prop === "_isServerSynced") return baseObj._isServerSynced;
2766
-
2767
2477
  if (prop === "update") {
2768
2478
  return (payload: UpdateArg<T>, opts?: UpdateOpts<T>) => {
2769
- // ADDED: Invalidate cache on update
2770
2479
  if (opts?.debounce) {
2771
2480
  debounce(() => {
2772
2481
  updateFn(effectiveSetState, payload, path, "");
@@ -2785,19 +2494,16 @@ function createProxyHandler<T>(
2785
2494
  invalidateCachePath(path);
2786
2495
  };
2787
2496
  }
2788
-
2789
2497
  if (prop === "formElement") {
2790
- return (child: FormControl<T>, formOpts?: FormOptsType) => {
2791
- return (
2792
- <FormControlComponent<T>
2793
- setState={effectiveSetState}
2794
- stateKey={stateKey}
2795
- path={path}
2796
- child={child}
2797
- formOpts={formOpts}
2798
- />
2799
- );
2800
- };
2498
+ return (child: FormControl<T>, formOpts?: FormOptsType) => (
2499
+ <FormControlComponent<T>
2500
+ setState={effectiveSetState}
2501
+ stateKey={stateKey}
2502
+ path={path}
2503
+ child={child}
2504
+ formOpts={formOpts}
2505
+ />
2506
+ );
2801
2507
  }
2802
2508
 
2803
2509
  const nextPath = [...path, prop];
@@ -2809,12 +2515,10 @@ function createProxyHandler<T>(
2809
2515
  };
2810
2516
 
2811
2517
  const proxyInstance = new Proxy(baseFunction, handler);
2812
-
2813
2518
  shapeCache.set(cacheKey, {
2814
2519
  proxy: proxyInstance,
2815
2520
  stateVersion: stateVersion,
2816
2521
  });
2817
-
2818
2522
  return proxyInstance;
2819
2523
  }
2820
2524
 
@@ -2833,7 +2537,6 @@ export function $cogsSignal(proxy: {
2833
2537
 
2834
2538
  function SignalMapRenderer({
2835
2539
  proxy,
2836
-
2837
2540
  rebuildStateShape,
2838
2541
  }: {
2839
2542
  proxy: {
@@ -2847,30 +2550,26 @@ function SignalMapRenderer({
2847
2550
  arraySetter: any
2848
2551
  ) => ReactNode;
2849
2552
  };
2850
-
2851
2553
  rebuildStateShape: (
2852
2554
  currentState: any,
2853
2555
  path: string[],
2854
- meta?: { filtered?: string[][]; validIndices?: number[] }
2556
+ meta?: { validIds?: string[] }
2855
2557
  ) => any;
2856
2558
  }) {
2857
2559
  const value = getGlobalStore().getNestedState(proxy._stateKey, proxy._path);
2560
+ if (!Array.isArray(value)) return null;
2858
2561
 
2859
- if (!Array.isArray(value)) {
2860
- return null;
2861
- }
2862
2562
  const arraySetter = rebuildStateShape(
2863
2563
  value,
2864
2564
  proxy._path
2865
2565
  ) as ArrayEndType<any>;
2866
- // Use existing global state management
2867
2566
  return arraySetter.stateMapNoRender(
2868
- (item, setter, index, value, arraysetter) => {
2869
- // Execute map function in React context with existing state/proxies
2870
- return proxy._mapFn(item, setter, index, value, arraysetter);
2567
+ (item, setter, index, array, arraySetter) => {
2568
+ return proxy._mapFn(item, setter, index, array, arraySetter);
2871
2569
  }
2872
2570
  );
2873
2571
  }
2572
+
2874
2573
  function SignalRenderer({
2875
2574
  proxy,
2876
2575
  }: {
@@ -2907,12 +2606,10 @@ function SignalRenderer({
2907
2606
 
2908
2607
  getGlobalStore.getState().addSignalElement(signalId, elementInfo);
2909
2608
 
2910
- // Get the raw value from the store
2911
2609
  const value = getGlobalStore
2912
2610
  .getState()
2913
2611
  .getNestedState(proxy._stateKey, proxy._path);
2914
-
2915
- let displayValue;
2612
+ let displayValue = value;
2916
2613
  if (proxy._effect) {
2917
2614
  try {
2918
2615
  displayValue = new Function(
@@ -2920,11 +2617,8 @@ function SignalRenderer({
2920
2617
  `return (${proxy._effect})(state)`
2921
2618
  )(value);
2922
2619
  } catch (err) {
2923
- console.error("Error evaluating effect function during mount:", err);
2924
- displayValue = value; // Fallback to raw value
2620
+ console.error("Error evaluating effect function:", err);
2925
2621
  }
2926
- } else {
2927
- displayValue = value;
2928
2622
  }
2929
2623
 
2930
2624
  if (displayValue !== null && typeof displayValue === "object") {
@@ -2941,6 +2635,7 @@ function SignalRenderer({
2941
2635
  "data-signal-id": signalId,
2942
2636
  });
2943
2637
  }
2638
+
2944
2639
  export function $cogsSignalStore(proxy: {
2945
2640
  _path: string[];
2946
2641
  _stateKey: string;
@@ -2949,13 +2644,14 @@ export function $cogsSignalStore(proxy: {
2949
2644
  (notify) => {
2950
2645
  const stateEntry = getGlobalStore
2951
2646
  .getState()
2952
- .stateComponents.get(proxy._stateKey) || {
2953
- components: new Map(),
2954
- };
2647
+ .stateComponents.get(proxy._stateKey) || { components: new Map() };
2955
2648
  stateEntry.components.set(proxy._stateKey, {
2956
2649
  forceUpdate: notify,
2957
2650
  paths: new Set([proxy._path.join(".")]),
2958
2651
  });
2652
+ getGlobalStore
2653
+ .getState()
2654
+ .stateComponents.set(proxy._stateKey, stateEntry);
2959
2655
  return () => stateEntry.components.delete(proxy._stateKey);
2960
2656
  },
2961
2657
  () => getGlobalStore.getState().getNestedState(proxy._stateKey, proxy._path)
@@ -2963,12 +2659,10 @@ export function $cogsSignalStore(proxy: {
2963
2659
  return createElement("text", {}, String(value));
2964
2660
  }
2965
2661
 
2966
- // Modified CogsItemWrapper that stores the DOM ref
2967
2662
  function CogsItemWrapper({
2968
2663
  stateKey,
2969
2664
  itemComponentId,
2970
2665
  itemPath,
2971
- formOpts,
2972
2666
  children,
2973
2667
  }: {
2974
2668
  stateKey: string;
@@ -2977,16 +2671,11 @@ function CogsItemWrapper({
2977
2671
  formOpts?: FormOptsType;
2978
2672
  children: React.ReactNode;
2979
2673
  }) {
2980
- // This hook handles the re-rendering when the item's own data changes.
2981
2674
  const [, forceUpdate] = useState({});
2982
- // This hook measures the element.
2983
2675
  const [measureRef, bounds] = useMeasure();
2984
- // Store the actual DOM element
2985
2676
  const elementRef = useRef<HTMLDivElement | null>(null);
2986
- // This ref prevents sending the same height update repeatedly.
2987
2677
  const lastReportedHeight = useRef<number | null>(null);
2988
2678
 
2989
- // Combine both refs
2990
2679
  const setRefs = useCallback(
2991
2680
  (element: HTMLDivElement | null) => {
2992
2681
  measureRef(element);
@@ -2995,50 +2684,32 @@ function CogsItemWrapper({
2995
2684
  [measureRef]
2996
2685
  );
2997
2686
 
2998
- // This is the primary effect for this component.
2999
2687
  useEffect(() => {
3000
- // We only report a height if it's a valid number AND it's different
3001
- // from the last height we reported. This prevents infinite loops.
3002
2688
  if (bounds.height > 0 && bounds.height !== lastReportedHeight.current) {
3003
- // Store the new height so we don't report it again.
3004
2689
  lastReportedHeight.current = bounds.height;
3005
-
3006
- // Call the store function to save the height AND the ref
3007
2690
  getGlobalStore.getState().setShadowMetadata(stateKey, itemPath, {
3008
- virtualizer: {
3009
- itemHeight: bounds.height,
3010
- domRef: elementRef.current, // Store the actual DOM element reference
3011
- },
2691
+ virtualizer: { itemHeight: bounds.height, domRef: elementRef.current },
3012
2692
  });
3013
2693
  }
3014
- }, [bounds.height, stateKey, itemPath]); // Removed ref.current as dependency
2694
+ }, [bounds.height, stateKey, itemPath]);
3015
2695
 
3016
- // This effect handles subscribing the item to its own data path for updates.
3017
2696
  useLayoutEffect(() => {
3018
2697
  const fullComponentId = `${stateKey}////${itemComponentId}`;
3019
2698
  const stateEntry = getGlobalStore
3020
2699
  .getState()
3021
- .stateComponents.get(stateKey) || {
3022
- components: new Map(),
3023
- };
3024
-
2700
+ .stateComponents.get(stateKey) || { components: new Map() };
3025
2701
  stateEntry.components.set(fullComponentId, {
3026
2702
  forceUpdate: () => forceUpdate({}),
3027
2703
  paths: new Set([itemPath.join(".")]),
3028
2704
  });
3029
-
3030
2705
  getGlobalStore.getState().stateComponents.set(stateKey, stateEntry);
3031
-
3032
2706
  return () => {
3033
2707
  const currentEntry = getGlobalStore
3034
2708
  .getState()
3035
2709
  .stateComponents.get(stateKey);
3036
- if (currentEntry) {
3037
- currentEntry.components.delete(fullComponentId);
3038
- }
2710
+ if (currentEntry) currentEntry.components.delete(fullComponentId);
3039
2711
  };
3040
2712
  }, [stateKey, itemComponentId, itemPath.join(".")]);
3041
2713
 
3042
- // The rendered output is a simple div that gets measured.
3043
2714
  return <div ref={setRefs}>{children}</div>;
3044
2715
  }