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