cogsbox-state 0.5.288 → 0.5.290
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 +681 -694
- package/dist/CogsState.jsx.map +1 -1
- package/dist/store.d.ts +1 -1
- package/dist/store.js +49 -65
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +73 -96
- package/src/store.ts +5 -33
package/src/CogsState.tsx
CHANGED
|
@@ -42,7 +42,7 @@ import useMeasure from "react-use-measure";
|
|
|
42
42
|
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
43
43
|
|
|
44
44
|
export type VirtualViewOptions = {
|
|
45
|
-
itemHeight
|
|
45
|
+
itemHeight: number;
|
|
46
46
|
overscan?: number;
|
|
47
47
|
stickToBottom?: boolean;
|
|
48
48
|
};
|
|
@@ -1039,8 +1039,6 @@ 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
|
-
|
|
1044
1042
|
setState(thisKey, (prevValue: TStateObject) => {
|
|
1045
1043
|
const payload = isFunction<TStateObject>(newStateOrFunction)
|
|
1046
1044
|
? newStateOrFunction(prevValue as TStateObject)
|
|
@@ -1049,7 +1047,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1049
1047
|
const signalId = `${thisKey}-${path.join(".")}`;
|
|
1050
1048
|
if (signalId) {
|
|
1051
1049
|
let isArrayOperation = false;
|
|
1052
|
-
let elements =
|
|
1050
|
+
let elements = getGlobalStore
|
|
1051
|
+
.getState()
|
|
1052
|
+
.signalDomElements.get(signalId);
|
|
1053
1053
|
|
|
1054
1054
|
if (
|
|
1055
1055
|
(!elements || elements.size === 0) &&
|
|
@@ -1062,7 +1062,9 @@ 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 =
|
|
1065
|
+
elements = getGlobalStore
|
|
1066
|
+
.getState()
|
|
1067
|
+
.signalDomElements.get(arraySignalId);
|
|
1066
1068
|
}
|
|
1067
1069
|
}
|
|
1068
1070
|
|
|
@@ -1087,7 +1089,32 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1087
1089
|
}
|
|
1088
1090
|
}
|
|
1089
1091
|
|
|
1090
|
-
|
|
1092
|
+
const shadowUpdate = () => {
|
|
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);
|
|
1091
1118
|
if (
|
|
1092
1119
|
updateObj.updateType === "update" &&
|
|
1093
1120
|
(validationKey || latestInitialOptionsRef.current?.validation?.key) &&
|
|
@@ -1137,7 +1164,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1137
1164
|
});
|
|
1138
1165
|
}
|
|
1139
1166
|
|
|
1140
|
-
const stateEntry =
|
|
1167
|
+
const stateEntry = getGlobalStore.getState().stateComponents.get(thisKey);
|
|
1141
1168
|
console.log("stateEntry >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", stateEntry);
|
|
1142
1169
|
if (stateEntry) {
|
|
1143
1170
|
const changedPaths = getDifferences(prevValue, payload);
|
|
@@ -1255,27 +1282,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1255
1282
|
newValue,
|
|
1256
1283
|
} satisfies UpdateTypeDetail;
|
|
1257
1284
|
|
|
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
|
-
|
|
1279
1285
|
setStateLog(thisKey, (prevLogs) => {
|
|
1280
1286
|
const logs = [...(prevLogs ?? []), newUpdate];
|
|
1281
1287
|
|
|
@@ -1316,7 +1322,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1316
1322
|
});
|
|
1317
1323
|
}
|
|
1318
1324
|
if (latestInitialOptionsRef.current?.serverSync) {
|
|
1319
|
-
const serverStateStore =
|
|
1325
|
+
const serverStateStore = getGlobalStore.getState().serverState[thisKey];
|
|
1320
1326
|
const serverSync = latestInitialOptionsRef.current?.serverSync;
|
|
1321
1327
|
setServerSyncActions(thisKey, {
|
|
1322
1328
|
syncKey:
|
|
@@ -1466,8 +1472,6 @@ function createProxyHandler<T>(
|
|
|
1466
1472
|
if (localStorage.getItem(storageKey)) {
|
|
1467
1473
|
localStorage.removeItem(storageKey);
|
|
1468
1474
|
}
|
|
1469
|
-
|
|
1470
|
-
console.log("udpating intial State", stateKey, newState);
|
|
1471
1475
|
startTransition(() => {
|
|
1472
1476
|
updateInitialStateGlobal(stateKey, newState);
|
|
1473
1477
|
getGlobalStore.getState().initializeShadowState(stateKey, newState);
|
|
@@ -1803,9 +1807,8 @@ function createProxyHandler<T>(
|
|
|
1803
1807
|
return (
|
|
1804
1808
|
options: VirtualViewOptions
|
|
1805
1809
|
): VirtualStateObjectResult<any[]> => {
|
|
1806
|
-
// --- CHANGE 1: itemHeight is now optional with a default ---
|
|
1807
1810
|
const {
|
|
1808
|
-
itemHeight
|
|
1811
|
+
itemHeight,
|
|
1809
1812
|
overscan = 5,
|
|
1810
1813
|
stickToBottom = false,
|
|
1811
1814
|
} = options;
|
|
@@ -1815,20 +1818,16 @@ function createProxyHandler<T>(
|
|
|
1815
1818
|
startIndex: 0,
|
|
1816
1819
|
endIndex: 10,
|
|
1817
1820
|
});
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
return metadata?.virtualizer?.itemHeight || itemHeight;
|
|
1826
|
-
},
|
|
1827
|
-
[itemHeight, stateKey, path]
|
|
1828
|
-
);
|
|
1829
|
-
|
|
1821
|
+
const getItemHeight = useCallback((index: number) => {
|
|
1822
|
+
const metadata = getGlobalStore
|
|
1823
|
+
.getState()
|
|
1824
|
+
.getShadowMetadata(stateKey, [...path, index.toString()]);
|
|
1825
|
+
return metadata?.virtualizer?.itemHeight || options.itemHeight;
|
|
1826
|
+
}, []);
|
|
1827
|
+
// --- State Tracking Refs ---
|
|
1830
1828
|
const isAtBottomRef = useRef(stickToBottom);
|
|
1831
1829
|
const previousTotalCountRef = useRef(0);
|
|
1830
|
+
// NEW: Ref to explicitly track if this is the component's first render cycle.
|
|
1832
1831
|
const isInitialMountRef = useRef(true);
|
|
1833
1832
|
|
|
1834
1833
|
const sourceArray = getGlobalStore().getNestedState(
|
|
@@ -1837,19 +1836,6 @@ function createProxyHandler<T>(
|
|
|
1837
1836
|
) as any[];
|
|
1838
1837
|
const totalCount = sourceArray.length;
|
|
1839
1838
|
|
|
1840
|
-
// --- CHANGE 3: Pre-calculate total height and item positions ---
|
|
1841
|
-
// This replaces all instances of `totalCount * itemHeight`.
|
|
1842
|
-
const { totalHeight, positions } = useMemo(() => {
|
|
1843
|
-
let currentHeight = 0;
|
|
1844
|
-
const pos: number[] = [];
|
|
1845
|
-
for (let i = 0; i < totalCount; i++) {
|
|
1846
|
-
pos[i] = currentHeight;
|
|
1847
|
-
currentHeight += getItemHeight(i);
|
|
1848
|
-
}
|
|
1849
|
-
return { totalHeight: currentHeight, positions: pos };
|
|
1850
|
-
}, [totalCount, getItemHeight]);
|
|
1851
|
-
|
|
1852
|
-
// This part is IDENTICAL to your original code
|
|
1853
1839
|
const virtualState = useMemo(() => {
|
|
1854
1840
|
const start = Math.max(0, range.startIndex);
|
|
1855
1841
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1876,36 +1862,21 @@ function createProxyHandler<T>(
|
|
|
1876
1862
|
const { scrollTop, clientHeight, scrollHeight } = container;
|
|
1877
1863
|
isAtBottomRef.current =
|
|
1878
1864
|
scrollHeight - scrollTop - clientHeight < 10;
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
let endIndex = startIndex;
|
|
1892
|
-
while (
|
|
1893
|
-
endIndex < totalCount &&
|
|
1894
|
-
positions[endIndex] &&
|
|
1895
|
-
positions[endIndex]! < scrollTop + clientHeight
|
|
1896
|
-
) {
|
|
1897
|
-
endIndex++;
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
startIndex = Math.max(0, startIndex - overscan);
|
|
1901
|
-
endIndex = Math.min(totalCount, endIndex + overscan);
|
|
1902
|
-
|
|
1865
|
+
const start = Math.max(
|
|
1866
|
+
0,
|
|
1867
|
+
Math.floor(scrollTop / itemHeight) - overscan
|
|
1868
|
+
);
|
|
1869
|
+
const end = Math.min(
|
|
1870
|
+
totalCount,
|
|
1871
|
+
Math.ceil((scrollTop + clientHeight) / itemHeight) +
|
|
1872
|
+
overscan
|
|
1873
|
+
);
|
|
1903
1874
|
setRange((prevRange) => {
|
|
1904
1875
|
if (
|
|
1905
|
-
prevRange.startIndex !==
|
|
1906
|
-
prevRange.endIndex !==
|
|
1876
|
+
prevRange.startIndex !== start ||
|
|
1877
|
+
prevRange.endIndex !== end
|
|
1907
1878
|
) {
|
|
1908
|
-
return { startIndex:
|
|
1879
|
+
return { startIndex: start, endIndex: end };
|
|
1909
1880
|
}
|
|
1910
1881
|
return prevRange;
|
|
1911
1882
|
});
|
|
@@ -1915,14 +1886,19 @@ function createProxyHandler<T>(
|
|
|
1915
1886
|
passive: true,
|
|
1916
1887
|
});
|
|
1917
1888
|
|
|
1918
|
-
//
|
|
1889
|
+
// --- THE CORRECTED DECISION LOGIC ---
|
|
1919
1890
|
if (stickToBottom) {
|
|
1920
1891
|
if (isInitialMountRef.current) {
|
|
1892
|
+
// SCENARIO 1: First render of the component.
|
|
1893
|
+
// Go to the bottom unconditionally. Use `auto` scroll for an instant jump.
|
|
1921
1894
|
container.scrollTo({
|
|
1922
1895
|
top: container.scrollHeight,
|
|
1923
1896
|
behavior: "auto",
|
|
1924
1897
|
});
|
|
1925
1898
|
} 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.
|
|
1926
1902
|
requestAnimationFrame(() => {
|
|
1927
1903
|
container.scrollTo({
|
|
1928
1904
|
top: container.scrollHeight,
|
|
@@ -1931,13 +1907,16 @@ function createProxyHandler<T>(
|
|
|
1931
1907
|
});
|
|
1932
1908
|
}
|
|
1933
1909
|
}
|
|
1910
|
+
|
|
1911
|
+
// After the logic runs, it's no longer the initial mount.
|
|
1934
1912
|
isInitialMountRef.current = false;
|
|
1913
|
+
|
|
1914
|
+
// Always run handleScroll once to set the initial visible window.
|
|
1935
1915
|
handleScroll();
|
|
1936
1916
|
|
|
1937
1917
|
return () =>
|
|
1938
1918
|
container.removeEventListener("scroll", handleScroll);
|
|
1939
|
-
|
|
1940
|
-
}, [totalCount, overscan, stickToBottom, positions]);
|
|
1919
|
+
}, [totalCount, itemHeight, overscan, stickToBottom]);
|
|
1941
1920
|
|
|
1942
1921
|
const scrollToBottom = useCallback(
|
|
1943
1922
|
(behavior: ScrollBehavior = "smooth") => {
|
|
@@ -1951,20 +1930,19 @@ function createProxyHandler<T>(
|
|
|
1951
1930
|
[]
|
|
1952
1931
|
);
|
|
1953
1932
|
|
|
1954
|
-
// --- CHANGE 5: Update scrollToIndex to use positions array ---
|
|
1955
1933
|
const scrollToIndex = useCallback(
|
|
1956
1934
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
1957
|
-
if (containerRef.current
|
|
1935
|
+
if (containerRef.current) {
|
|
1958
1936
|
containerRef.current.scrollTo({
|
|
1959
|
-
top:
|
|
1937
|
+
top: index * itemHeight,
|
|
1960
1938
|
behavior,
|
|
1961
1939
|
});
|
|
1962
1940
|
}
|
|
1963
1941
|
},
|
|
1964
|
-
[
|
|
1942
|
+
[itemHeight]
|
|
1965
1943
|
);
|
|
1966
1944
|
|
|
1967
|
-
//
|
|
1945
|
+
// Same virtualizer props as before
|
|
1968
1946
|
const virtualizerProps = {
|
|
1969
1947
|
outer: {
|
|
1970
1948
|
ref: containerRef,
|
|
@@ -1972,14 +1950,13 @@ function createProxyHandler<T>(
|
|
|
1972
1950
|
},
|
|
1973
1951
|
inner: {
|
|
1974
1952
|
style: {
|
|
1975
|
-
height: `${
|
|
1953
|
+
height: `${totalCount * itemHeight}px`,
|
|
1976
1954
|
position: "relative",
|
|
1977
1955
|
},
|
|
1978
1956
|
},
|
|
1979
1957
|
list: {
|
|
1980
1958
|
style: {
|
|
1981
|
-
|
|
1982
|
-
transform: `translateY(${positions[range.startIndex] || 0}px)`,
|
|
1959
|
+
transform: `translateY(${range.startIndex * itemHeight}px)`,
|
|
1983
1960
|
},
|
|
1984
1961
|
},
|
|
1985
1962
|
};
|
package/src/store.ts
CHANGED
|
@@ -102,11 +102,7 @@ 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: (
|
|
106
|
-
key: string,
|
|
107
|
-
arrayPath: string[],
|
|
108
|
-
newItem: any
|
|
109
|
-
) => void;
|
|
105
|
+
insertShadowArrayElement: (key: string, arrayPath: string[]) => void;
|
|
110
106
|
removeShadowArrayElement: (
|
|
111
107
|
key: string,
|
|
112
108
|
arrayPath: string[],
|
|
@@ -329,41 +325,17 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
329
325
|
});
|
|
330
326
|
},
|
|
331
327
|
|
|
332
|
-
insertShadowArrayElement: (
|
|
333
|
-
key: string,
|
|
334
|
-
arrayPath: string[],
|
|
335
|
-
newItem: any
|
|
336
|
-
) => {
|
|
328
|
+
insertShadowArrayElement: (key: string, arrayPath: string[]) => {
|
|
337
329
|
set((state) => {
|
|
338
330
|
const newShadow = { ...state.shadowStateStore };
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
newShadow[key] = JSON.parse(JSON.stringify(newShadow[key]));
|
|
342
|
-
|
|
343
|
-
let current: any = newShadow[key];
|
|
331
|
+
let current = newShadow[key];
|
|
344
332
|
|
|
345
333
|
for (const segment of arrayPath) {
|
|
346
|
-
current = current[segment];
|
|
347
|
-
if (!current) return state;
|
|
334
|
+
current = current?.[segment];
|
|
348
335
|
}
|
|
349
336
|
|
|
350
337
|
if (Array.isArray(current)) {
|
|
351
|
-
|
|
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));
|
|
338
|
+
current.push({});
|
|
367
339
|
}
|
|
368
340
|
|
|
369
341
|
return { shadowStateStore: newShadow };
|