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/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?: number;
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 = store.signalDomElements.get(signalId);
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 = store.signalDomElements.get(arraySignalId);
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
- console.log("shadowState", store.shadowStateStore);
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 = store.stateComponents.get(thisKey);
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 = store.serverState[thisKey];
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 = 50, // Serves as a fallback for unmeasured items
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
- // --- CHANGE 2: Add a helper to get heights from shadow store ---
1820
- const getItemHeight = useCallback(
1821
- (index: number): number => {
1822
- const metadata = getGlobalStore
1823
- .getState()
1824
- .getShadowMetadata(stateKey, [...path, index.toString()]);
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
- // --- CHANGE 4: Update scroll logic to use positions array ---
1881
- // This is the dynamic equivalent of `Math.floor(scrollTop / itemHeight)`.
1882
- let startIndex = 0;
1883
- // A simple loop is robust and easy to understand.
1884
- for (let i = 0; i < positions.length; i++) {
1885
- if (positions[i]! >= scrollTop) {
1886
- startIndex = i;
1887
- break;
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 !== startIndex ||
1906
- prevRange.endIndex !== endIndex
1876
+ prevRange.startIndex !== start ||
1877
+ prevRange.endIndex !== end
1907
1878
  ) {
1908
- return { startIndex: startIndex, endIndex: endIndex };
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
- // This logic is IDENTICAL to your original code
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
- // The dependencies are almost identical, just swapping itemHeight for `positions`
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 && positions[index] !== undefined) {
1935
+ if (containerRef.current) {
1958
1936
  containerRef.current.scrollTo({
1959
- top: positions[index], // Instead of `index * itemHeight`
1937
+ top: index * itemHeight,
1960
1938
  behavior,
1961
1939
  });
1962
1940
  }
1963
1941
  },
1964
- [positions] // Depends on `positions` now instead of `itemHeight`
1942
+ [itemHeight]
1965
1943
  );
1966
1944
 
1967
- // --- CHANGE 6: Update props to use dynamic totalHeight and offsets ---
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: `${totalHeight}px`, // Use calculated total height
1953
+ height: `${totalCount * itemHeight}px`,
1976
1954
  position: "relative",
1977
1955
  },
1978
1956
  },
1979
1957
  list: {
1980
1958
  style: {
1981
- // Use the pre-calculated position of the first visible item
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
- if (!newShadow[key]) return state;
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
- // 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));
338
+ current.push({});
367
339
  }
368
340
 
369
341
  return { shadowStateStore: newShadow };