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/dist/CogsState.d.ts +1 -1
- package/dist/CogsState.jsx +793 -857
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Functions.jsx +134 -136
- package/dist/Functions.jsx.map +1 -1
- package/dist/index.js +9 -8
- package/dist/store.d.ts +18 -8
- package/dist/store.js +165 -186
- package/dist/store.js.map +1 -1
- package/dist/utility.d.ts +1 -0
- package/dist/utility.js +165 -121
- package/dist/utility.js.map +1 -1
- package/package.json +7 -2
- package/src/CogsState.tsx +385 -694
- package/src/Functions.tsx +47 -39
- package/src/store.ts +172 -195
- package/src/utility.ts +88 -10
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<
|
|
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
|
-
|
|
1121
|
+
const getValidation = getValidationErrors(
|
|
1122
1122
|
latestInitialOptionsRef.current?.validation?.key +
|
|
1123
1123
|
"." +
|
|
1124
1124
|
arrayWithoutIndex.join(".")
|
|
1125
1125
|
);
|
|
1126
1126
|
|
|
1127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
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
|
-
|
|
1269
|
-
|
|
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
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
store.removeShadowArrayElement(thisKey,
|
|
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
|
-
|
|
1287
|
-
const aggregatedLogs = logs.reduce((acc, log) => {
|
|
1289
|
+
logs.forEach((log) => {
|
|
1288
1290
|
const uniqueKey = `${log.stateKey}:${JSON.stringify(log.path)}`;
|
|
1289
|
-
const existing =
|
|
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;
|
|
1295
|
-
existing.oldValue = existing.oldValue ?? log.oldValue;
|
|
1296
|
-
existing.updateType = log.updateType;
|
|
1295
|
+
existing.newValue = log.newValue;
|
|
1296
|
+
existing.oldValue = existing.oldValue ?? log.oldValue;
|
|
1297
|
+
existing.updateType = log.updateType;
|
|
1297
1298
|
} else {
|
|
1298
|
-
|
|
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?: {
|
|
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
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1671
|
-
if (response?.success && sync.onSuccess) {
|
|
1630
|
+
if (response?.success && sync.onSuccess)
|
|
1672
1631
|
sync.onSuccess(response.data);
|
|
1673
|
-
|
|
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
|
-
|
|
1698
|
-
|
|
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
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
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
|
|
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
|
|
1730
|
+
return globallySelectedIndex;
|
|
1802
1731
|
};
|
|
1803
1732
|
}
|
|
1804
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
|
1852
|
-
|
|
1853
|
-
|
|
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
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
);
|
|
1872
|
-
|
|
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
|
-
|
|
1812
|
+
validIds: slicedIds,
|
|
1876
1813
|
});
|
|
1877
|
-
}, [
|
|
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
|
-
|
|
1887
|
-
const
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
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
|
-
|
|
1848
|
+
hasNewItems &&
|
|
1914
1849
|
wasAtBottomRef.current &&
|
|
1915
1850
|
!userHasScrolledAwayRef.current
|
|
1916
1851
|
) {
|
|
1917
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 === "
|
|
1962
|
+
if (prop === "stateMap") {
|
|
2107
1963
|
return (
|
|
2108
1964
|
callbackfn: (
|
|
2109
|
-
value:
|
|
2110
|
-
setter:
|
|
1965
|
+
value: any,
|
|
1966
|
+
setter: any,
|
|
2111
1967
|
index: number,
|
|
2112
|
-
array:
|
|
2113
|
-
arraySetter:
|
|
2114
|
-
) =>
|
|
1968
|
+
array: any,
|
|
1969
|
+
arraySetter: any
|
|
1970
|
+
) => void
|
|
2115
1971
|
) => {
|
|
2116
1972
|
const arrayToMap = currentState as any[];
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1983
|
+
itemSetter,
|
|
2134
1984
|
index,
|
|
2135
|
-
currentState
|
|
2136
|
-
|
|
1985
|
+
currentState,
|
|
1986
|
+
arraySetter
|
|
2137
1987
|
);
|
|
2138
1988
|
});
|
|
2139
1989
|
};
|
|
2140
1990
|
}
|
|
2141
|
-
if (prop === "
|
|
1991
|
+
if (prop === "stateMapNoRender") {
|
|
2142
1992
|
return (
|
|
2143
1993
|
callbackfn: (
|
|
2144
|
-
value:
|
|
2145
|
-
setter:
|
|
1994
|
+
value: any,
|
|
1995
|
+
setter: any,
|
|
2146
1996
|
index: number,
|
|
2147
|
-
array:
|
|
2148
|
-
arraySetter:
|
|
1997
|
+
array: any,
|
|
1998
|
+
arraySetter: any
|
|
2149
1999
|
) => void
|
|
2150
2000
|
) => {
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
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
|
-
|
|
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:
|
|
2166
|
-
setter:
|
|
2030
|
+
value: any,
|
|
2031
|
+
setter: any,
|
|
2167
2032
|
index: { localIndex: number; originalIndex: number },
|
|
2168
|
-
array:
|
|
2169
|
-
arraySetter:
|
|
2170
|
-
) =>
|
|
2171
|
-
formOpts?: FormOptsType
|
|
2033
|
+
array: any,
|
|
2034
|
+
arraySetter: any
|
|
2035
|
+
) => ReactNode
|
|
2172
2036
|
) => {
|
|
2173
|
-
const arrayToMap =
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
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
|
|
2189
|
-
const
|
|
2190
|
-
|
|
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(".")}-${
|
|
2055
|
+
const itemComponentId = `${componentId}-${path.join(".")}-${itemId}`;
|
|
2193
2056
|
|
|
2194
2057
|
return createElement(CogsItemWrapper, {
|
|
2195
|
-
key:
|
|
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
|
-
|
|
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
|
|
2230
|
-
|
|
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
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
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
|
-
|
|
2320
|
-
|
|
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 === "
|
|
2339
|
-
return (
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
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
|
-
|
|
2350
|
-
const
|
|
2351
|
-
|
|
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
|
|
2358
|
-
|
|
2359
|
-
({ item }) => item[thisKey] === thisValue
|
|
2240
|
+
const foundItem = (currentState as any[]).find(
|
|
2241
|
+
(item) => item[thisKey] === thisValue
|
|
2360
2242
|
);
|
|
2361
|
-
if (!
|
|
2362
|
-
const
|
|
2363
|
-
|
|
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
|
|
2388
|
-
|
|
2389
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2426
|
-
.
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
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
|
-
|
|
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
|
|
2441
|
-
const
|
|
2331
|
+
const itemId = path[path.length - 1];
|
|
2332
|
+
const orderedIds = getOrderedIds(parentPath);
|
|
2333
|
+
const thisIndex = orderedIds?.indexOf(itemId!);
|
|
2442
2334
|
|
|
2443
|
-
if (
|
|
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
|
|
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
|
|
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
|
-
|
|
2635
|
-
|
|
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
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
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
|
-
|
|
2698
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
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?: {
|
|
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,
|
|
2849
|
-
|
|
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
|
|
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]);
|
|
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
|
}
|