cogsbox-state 0.5.290 → 0.5.292
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.jsx +627 -614
- package/dist/CogsState.jsx.map +1 -1
- package/dist/store.d.ts +1 -1
- package/dist/store.js +65 -49
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +94 -72
- package/src/store.ts +33 -6
package/src/CogsState.tsx
CHANGED
|
@@ -1039,6 +1039,8 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1039
1039
|
const pathKey = `${thisKey}-${path.join(".")}`;
|
|
1040
1040
|
componentUpdatesRef.current.add(pathKey);
|
|
1041
1041
|
}
|
|
1042
|
+
const store = getGlobalStore.getState();
|
|
1043
|
+
|
|
1042
1044
|
setState(thisKey, (prevValue: TStateObject) => {
|
|
1043
1045
|
const payload = isFunction<TStateObject>(newStateOrFunction)
|
|
1044
1046
|
? newStateOrFunction(prevValue as TStateObject)
|
|
@@ -1047,9 +1049,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1047
1049
|
const signalId = `${thisKey}-${path.join(".")}`;
|
|
1048
1050
|
if (signalId) {
|
|
1049
1051
|
let isArrayOperation = false;
|
|
1050
|
-
let elements =
|
|
1051
|
-
.getState()
|
|
1052
|
-
.signalDomElements.get(signalId);
|
|
1052
|
+
let elements = store.signalDomElements.get(signalId);
|
|
1053
1053
|
|
|
1054
1054
|
if (
|
|
1055
1055
|
(!elements || elements.size === 0) &&
|
|
@@ -1062,9 +1062,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1062
1062
|
if (Array.isArray(arrayValue)) {
|
|
1063
1063
|
isArrayOperation = true;
|
|
1064
1064
|
const arraySignalId = `${thisKey}-${arrayPath.join(".")}`;
|
|
1065
|
-
elements =
|
|
1066
|
-
.getState()
|
|
1067
|
-
.signalDomElements.get(arraySignalId);
|
|
1065
|
+
elements = store.signalDomElements.get(arraySignalId);
|
|
1068
1066
|
}
|
|
1069
1067
|
}
|
|
1070
1068
|
|
|
@@ -1089,32 +1087,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1089
1087
|
}
|
|
1090
1088
|
}
|
|
1091
1089
|
|
|
1092
|
-
|
|
1093
|
-
const store = getGlobalStore.getState();
|
|
1094
|
-
|
|
1095
|
-
switch (updateObj.updateType) {
|
|
1096
|
-
case "update":
|
|
1097
|
-
// For updates, just mirror the structure at the path
|
|
1098
|
-
store.updateShadowAtPath(thisKey, path, payload);
|
|
1099
|
-
break;
|
|
1100
|
-
|
|
1101
|
-
case "insert":
|
|
1102
|
-
// For array insert, add empty element to shadow array
|
|
1103
|
-
const parentPath = path.slice(0, -1);
|
|
1104
|
-
store.insertShadowArrayElement(thisKey, parentPath);
|
|
1105
|
-
break;
|
|
1106
|
-
|
|
1107
|
-
case "cut":
|
|
1108
|
-
// For array cut, remove element from shadow array
|
|
1109
|
-
const arrayPath = path.slice(0, -1);
|
|
1110
|
-
const index = parseInt(path[path.length - 1]!);
|
|
1111
|
-
store.removeShadowArrayElement(thisKey, arrayPath, index);
|
|
1112
|
-
break;
|
|
1113
|
-
}
|
|
1114
|
-
};
|
|
1115
|
-
|
|
1116
|
-
shadowUpdate();
|
|
1117
|
-
console.log("shadowState", getGlobalStore.getState().shadowStateStore);
|
|
1090
|
+
console.log("shadowState", store.shadowStateStore);
|
|
1118
1091
|
if (
|
|
1119
1092
|
updateObj.updateType === "update" &&
|
|
1120
1093
|
(validationKey || latestInitialOptionsRef.current?.validation?.key) &&
|
|
@@ -1164,7 +1137,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1164
1137
|
});
|
|
1165
1138
|
}
|
|
1166
1139
|
|
|
1167
|
-
const stateEntry =
|
|
1140
|
+
const stateEntry = store.stateComponents.get(thisKey);
|
|
1168
1141
|
console.log("stateEntry >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", stateEntry);
|
|
1169
1142
|
if (stateEntry) {
|
|
1170
1143
|
const changedPaths = getDifferences(prevValue, payload);
|
|
@@ -1282,6 +1255,27 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1282
1255
|
newValue,
|
|
1283
1256
|
} satisfies UpdateTypeDetail;
|
|
1284
1257
|
|
|
1258
|
+
switch (updateObj.updateType) {
|
|
1259
|
+
case "update":
|
|
1260
|
+
// For updates, just mirror the structure at the path
|
|
1261
|
+
store.updateShadowAtPath(thisKey, path, payload);
|
|
1262
|
+
break;
|
|
1263
|
+
|
|
1264
|
+
case "insert":
|
|
1265
|
+
// For array insert, add empty element to shadow array
|
|
1266
|
+
|
|
1267
|
+
const parentPath = path.slice(0, -1);
|
|
1268
|
+
store.insertShadowArrayElement(thisKey, parentPath, newValue);
|
|
1269
|
+
break;
|
|
1270
|
+
|
|
1271
|
+
case "cut":
|
|
1272
|
+
// For array cut, remove element from shadow array
|
|
1273
|
+
const arrayPath = path.slice(0, -1);
|
|
1274
|
+
const index = parseInt(path[path.length - 1]!);
|
|
1275
|
+
store.removeShadowArrayElement(thisKey, arrayPath, index);
|
|
1276
|
+
break;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1285
1279
|
setStateLog(thisKey, (prevLogs) => {
|
|
1286
1280
|
const logs = [...(prevLogs ?? []), newUpdate];
|
|
1287
1281
|
|
|
@@ -1322,7 +1316,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1322
1316
|
});
|
|
1323
1317
|
}
|
|
1324
1318
|
if (latestInitialOptionsRef.current?.serverSync) {
|
|
1325
|
-
const serverStateStore =
|
|
1319
|
+
const serverStateStore = store.serverState[thisKey];
|
|
1326
1320
|
const serverSync = latestInitialOptionsRef.current?.serverSync;
|
|
1327
1321
|
setServerSyncActions(thisKey, {
|
|
1328
1322
|
syncKey:
|
|
@@ -1564,6 +1558,7 @@ function createProxyHandler<T>(
|
|
|
1564
1558
|
"_stateKey",
|
|
1565
1559
|
"getComponents",
|
|
1566
1560
|
]);
|
|
1561
|
+
|
|
1567
1562
|
if (
|
|
1568
1563
|
prop !== "then" &&
|
|
1569
1564
|
!prop.startsWith("$") &&
|
|
@@ -1807,8 +1802,9 @@ function createProxyHandler<T>(
|
|
|
1807
1802
|
return (
|
|
1808
1803
|
options: VirtualViewOptions
|
|
1809
1804
|
): VirtualStateObjectResult<any[]> => {
|
|
1805
|
+
// --- CHANGE 1: itemHeight is now optional with a default ---
|
|
1810
1806
|
const {
|
|
1811
|
-
itemHeight,
|
|
1807
|
+
itemHeight = 50, // Serves as a fallback for unmeasured items
|
|
1812
1808
|
overscan = 5,
|
|
1813
1809
|
stickToBottom = false,
|
|
1814
1810
|
} = options;
|
|
@@ -1818,16 +1814,20 @@ function createProxyHandler<T>(
|
|
|
1818
1814
|
startIndex: 0,
|
|
1819
1815
|
endIndex: 10,
|
|
1820
1816
|
});
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1817
|
+
|
|
1818
|
+
// --- CHANGE 2: Add a helper to get heights from shadow store ---
|
|
1819
|
+
const getItemHeight = useCallback(
|
|
1820
|
+
(index: number): number => {
|
|
1821
|
+
const metadata = getGlobalStore
|
|
1822
|
+
.getState()
|
|
1823
|
+
.getShadowMetadata(stateKey, [...path, index.toString()]);
|
|
1824
|
+
return metadata?.virtualizer?.itemHeight || itemHeight;
|
|
1825
|
+
},
|
|
1826
|
+
[itemHeight, stateKey, path]
|
|
1827
|
+
);
|
|
1828
|
+
|
|
1828
1829
|
const isAtBottomRef = useRef(stickToBottom);
|
|
1829
1830
|
const previousTotalCountRef = useRef(0);
|
|
1830
|
-
// NEW: Ref to explicitly track if this is the component's first render cycle.
|
|
1831
1831
|
const isInitialMountRef = useRef(true);
|
|
1832
1832
|
|
|
1833
1833
|
const sourceArray = getGlobalStore().getNestedState(
|
|
@@ -1836,6 +1836,19 @@ function createProxyHandler<T>(
|
|
|
1836
1836
|
) as any[];
|
|
1837
1837
|
const totalCount = sourceArray.length;
|
|
1838
1838
|
|
|
1839
|
+
// --- CHANGE 3: Pre-calculate total height and item positions ---
|
|
1840
|
+
// This replaces all instances of `totalCount * itemHeight`.
|
|
1841
|
+
const { totalHeight, positions } = useMemo(() => {
|
|
1842
|
+
let currentHeight = 0;
|
|
1843
|
+
const pos: number[] = [];
|
|
1844
|
+
for (let i = 0; i < totalCount; i++) {
|
|
1845
|
+
pos[i] = currentHeight;
|
|
1846
|
+
currentHeight += getItemHeight(i);
|
|
1847
|
+
}
|
|
1848
|
+
return { totalHeight: currentHeight, positions: pos };
|
|
1849
|
+
}, [totalCount, getItemHeight]);
|
|
1850
|
+
|
|
1851
|
+
// This part is IDENTICAL to your original code
|
|
1839
1852
|
const virtualState = useMemo(() => {
|
|
1840
1853
|
const start = Math.max(0, range.startIndex);
|
|
1841
1854
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1862,21 +1875,36 @@ function createProxyHandler<T>(
|
|
|
1862
1875
|
const { scrollTop, clientHeight, scrollHeight } = container;
|
|
1863
1876
|
isAtBottomRef.current =
|
|
1864
1877
|
scrollHeight - scrollTop - clientHeight < 10;
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1878
|
+
|
|
1879
|
+
// --- CHANGE 4: Update scroll logic to use positions array ---
|
|
1880
|
+
// This is the dynamic equivalent of `Math.floor(scrollTop / itemHeight)`.
|
|
1881
|
+
let startIndex = 0;
|
|
1882
|
+
// A simple loop is robust and easy to understand.
|
|
1883
|
+
for (let i = 0; i < positions.length; i++) {
|
|
1884
|
+
if (positions[i]! >= scrollTop) {
|
|
1885
|
+
startIndex = i;
|
|
1886
|
+
break;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
let endIndex = startIndex;
|
|
1891
|
+
while (
|
|
1892
|
+
endIndex < totalCount &&
|
|
1893
|
+
positions[endIndex] &&
|
|
1894
|
+
positions[endIndex]! < scrollTop + clientHeight
|
|
1895
|
+
) {
|
|
1896
|
+
endIndex++;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
startIndex = Math.max(0, startIndex - overscan);
|
|
1900
|
+
endIndex = Math.min(totalCount, endIndex + overscan);
|
|
1901
|
+
|
|
1874
1902
|
setRange((prevRange) => {
|
|
1875
1903
|
if (
|
|
1876
|
-
prevRange.startIndex !==
|
|
1877
|
-
prevRange.endIndex !==
|
|
1904
|
+
prevRange.startIndex !== startIndex ||
|
|
1905
|
+
prevRange.endIndex !== endIndex
|
|
1878
1906
|
) {
|
|
1879
|
-
return { startIndex:
|
|
1907
|
+
return { startIndex: startIndex, endIndex: endIndex };
|
|
1880
1908
|
}
|
|
1881
1909
|
return prevRange;
|
|
1882
1910
|
});
|
|
@@ -1886,19 +1914,14 @@ function createProxyHandler<T>(
|
|
|
1886
1914
|
passive: true,
|
|
1887
1915
|
});
|
|
1888
1916
|
|
|
1889
|
-
//
|
|
1917
|
+
// This logic is IDENTICAL to your original code
|
|
1890
1918
|
if (stickToBottom) {
|
|
1891
1919
|
if (isInitialMountRef.current) {
|
|
1892
|
-
// SCENARIO 1: First render of the component.
|
|
1893
|
-
// Go to the bottom unconditionally. Use `auto` scroll for an instant jump.
|
|
1894
1920
|
container.scrollTo({
|
|
1895
1921
|
top: container.scrollHeight,
|
|
1896
1922
|
behavior: "auto",
|
|
1897
1923
|
});
|
|
1898
1924
|
} else if (wasAtBottom && listGrew) {
|
|
1899
|
-
// SCENARIO 2: Subsequent renders (new messages arrive).
|
|
1900
|
-
// Only scroll if the user was already at the bottom.
|
|
1901
|
-
// Use `smooth` for a nice animated scroll for new messages.
|
|
1902
1925
|
requestAnimationFrame(() => {
|
|
1903
1926
|
container.scrollTo({
|
|
1904
1927
|
top: container.scrollHeight,
|
|
@@ -1907,16 +1930,13 @@ function createProxyHandler<T>(
|
|
|
1907
1930
|
});
|
|
1908
1931
|
}
|
|
1909
1932
|
}
|
|
1910
|
-
|
|
1911
|
-
// After the logic runs, it's no longer the initial mount.
|
|
1912
1933
|
isInitialMountRef.current = false;
|
|
1913
|
-
|
|
1914
|
-
// Always run handleScroll once to set the initial visible window.
|
|
1915
1934
|
handleScroll();
|
|
1916
1935
|
|
|
1917
1936
|
return () =>
|
|
1918
1937
|
container.removeEventListener("scroll", handleScroll);
|
|
1919
|
-
|
|
1938
|
+
// The dependencies are almost identical, just swapping itemHeight for `positions`
|
|
1939
|
+
}, [totalCount, overscan, stickToBottom, positions]);
|
|
1920
1940
|
|
|
1921
1941
|
const scrollToBottom = useCallback(
|
|
1922
1942
|
(behavior: ScrollBehavior = "smooth") => {
|
|
@@ -1930,19 +1950,20 @@ function createProxyHandler<T>(
|
|
|
1930
1950
|
[]
|
|
1931
1951
|
);
|
|
1932
1952
|
|
|
1953
|
+
// --- CHANGE 5: Update scrollToIndex to use positions array ---
|
|
1933
1954
|
const scrollToIndex = useCallback(
|
|
1934
1955
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
1935
|
-
if (containerRef.current) {
|
|
1956
|
+
if (containerRef.current && positions[index] !== undefined) {
|
|
1936
1957
|
containerRef.current.scrollTo({
|
|
1937
|
-
top: index * itemHeight
|
|
1958
|
+
top: positions[index], // Instead of `index * itemHeight`
|
|
1938
1959
|
behavior,
|
|
1939
1960
|
});
|
|
1940
1961
|
}
|
|
1941
1962
|
},
|
|
1942
|
-
[itemHeight
|
|
1963
|
+
[positions] // Depends on `positions` now instead of `itemHeight`
|
|
1943
1964
|
);
|
|
1944
1965
|
|
|
1945
|
-
//
|
|
1966
|
+
// --- CHANGE 6: Update props to use dynamic totalHeight and offsets ---
|
|
1946
1967
|
const virtualizerProps = {
|
|
1947
1968
|
outer: {
|
|
1948
1969
|
ref: containerRef,
|
|
@@ -1950,13 +1971,14 @@ function createProxyHandler<T>(
|
|
|
1950
1971
|
},
|
|
1951
1972
|
inner: {
|
|
1952
1973
|
style: {
|
|
1953
|
-
height: `${
|
|
1974
|
+
height: `${totalHeight}px`, // Use calculated total height
|
|
1954
1975
|
position: "relative",
|
|
1955
1976
|
},
|
|
1956
1977
|
},
|
|
1957
1978
|
list: {
|
|
1958
1979
|
style: {
|
|
1959
|
-
|
|
1980
|
+
// Use the pre-calculated position of the first visible item
|
|
1981
|
+
transform: `translateY(${positions[range.startIndex] || 0}px)`,
|
|
1960
1982
|
},
|
|
1961
1983
|
},
|
|
1962
1984
|
};
|
package/src/store.ts
CHANGED
|
@@ -102,7 +102,11 @@ export type CogsGlobalState = {
|
|
|
102
102
|
shadowStateStore: { [key: string]: any };
|
|
103
103
|
initializeShadowState: (key: string, initialState: any) => void;
|
|
104
104
|
updateShadowAtPath: (key: string, path: string[], newValue: any) => void;
|
|
105
|
-
insertShadowArrayElement: (
|
|
105
|
+
insertShadowArrayElement: (
|
|
106
|
+
key: string,
|
|
107
|
+
arrayPath: string[],
|
|
108
|
+
newItem: any
|
|
109
|
+
) => void;
|
|
106
110
|
removeShadowArrayElement: (
|
|
107
111
|
key: string,
|
|
108
112
|
arrayPath: string[],
|
|
@@ -325,23 +329,46 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
325
329
|
});
|
|
326
330
|
},
|
|
327
331
|
|
|
328
|
-
insertShadowArrayElement: (
|
|
332
|
+
insertShadowArrayElement: (
|
|
333
|
+
key: string,
|
|
334
|
+
arrayPath: string[],
|
|
335
|
+
newItem: any
|
|
336
|
+
) => {
|
|
329
337
|
set((state) => {
|
|
330
338
|
const newShadow = { ...state.shadowStateStore };
|
|
331
|
-
|
|
339
|
+
if (!newShadow[key]) return state;
|
|
340
|
+
|
|
341
|
+
newShadow[key] = JSON.parse(JSON.stringify(newShadow[key]));
|
|
342
|
+
|
|
343
|
+
let current: any = newShadow[key];
|
|
332
344
|
|
|
333
345
|
for (const segment of arrayPath) {
|
|
334
|
-
current = current
|
|
346
|
+
current = current[segment];
|
|
347
|
+
if (!current) return state;
|
|
335
348
|
}
|
|
336
349
|
|
|
337
350
|
if (Array.isArray(current)) {
|
|
338
|
-
|
|
351
|
+
// Create shadow structure based on the actual new item
|
|
352
|
+
const createShadowStructure = (obj: any): any => {
|
|
353
|
+
if (Array.isArray(obj)) {
|
|
354
|
+
return obj.map((item) => createShadowStructure(item));
|
|
355
|
+
}
|
|
356
|
+
if (typeof obj === "object" && obj !== null) {
|
|
357
|
+
const shadow: any = {};
|
|
358
|
+
for (const k in obj) {
|
|
359
|
+
shadow[k] = createShadowStructure(obj[k]);
|
|
360
|
+
}
|
|
361
|
+
return shadow;
|
|
362
|
+
}
|
|
363
|
+
return {}; // Leaf nodes get empty object for metadata
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
current.push(createShadowStructure(newItem));
|
|
339
367
|
}
|
|
340
368
|
|
|
341
369
|
return { shadowStateStore: newShadow };
|
|
342
370
|
});
|
|
343
371
|
},
|
|
344
|
-
|
|
345
372
|
removeShadowArrayElement: (
|
|
346
373
|
key: string,
|
|
347
374
|
arrayPath: string[],
|