cogsbox-state 0.5.294 → 0.5.296

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-state",
3
- "version": "0.5.294",
3
+ "version": "0.5.296",
4
4
  "description": "React state management library with form controls and server sync",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/CogsState.tsx CHANGED
@@ -1814,20 +1814,6 @@ function createProxyHandler<T>(
1814
1814
  endIndex: 10,
1815
1815
  });
1816
1816
 
1817
- // --- State Tracking Refs for Stability ---
1818
- const isAtBottomRef = useRef(stickToBottom);
1819
- // Store the scroll position before a new item is added
1820
- const scrollOffsetRef = useRef(0);
1821
- // Ref to track if the list has grown, to trigger scroll correction
1822
- const listGrewRef = useRef(false);
1823
-
1824
- const sourceArray = getGlobalStore().getNestedState(
1825
- stateKey,
1826
- path
1827
- ) as any[];
1828
- const totalCount = sourceArray.length;
1829
-
1830
- // Helper to get measured heights or the default
1831
1817
  const getItemHeight = useCallback(
1832
1818
  (index: number): number => {
1833
1819
  const metadata = getGlobalStore
@@ -1838,20 +1824,28 @@ function createProxyHandler<T>(
1838
1824
  [itemHeight, stateKey, path]
1839
1825
  );
1840
1826
 
1841
- // Pre-calculate total height and the top offset of each item
1827
+ const isAtBottomRef = useRef(stickToBottom);
1828
+ const previousTotalCountRef = useRef(0);
1829
+ const isInitialMountRef = useRef(true);
1830
+
1831
+ const sourceArray = getGlobalStore().getNestedState(
1832
+ stateKey,
1833
+ path
1834
+ ) as any[];
1835
+ const totalCount = sourceArray.length;
1836
+
1842
1837
  const { totalHeight, positions } = useMemo(() => {
1843
- let currentHeight = 0;
1838
+ let height = 0;
1844
1839
  const pos: number[] = [];
1845
1840
  for (let i = 0; i < totalCount; i++) {
1846
- pos[i] = currentHeight;
1847
- currentHeight += getItemHeight(i);
1841
+ pos[i] = height;
1842
+ height += getItemHeight(i);
1843
+ console.log("height", getItemHeight(i), height);
1848
1844
  }
1849
-
1850
- console.log("totalHeight", currentHeight);
1851
- return { totalHeight: currentHeight, positions: pos };
1845
+ return { totalHeight: height, positions: pos };
1852
1846
  }, [totalCount, getItemHeight]);
1853
1847
 
1854
- // This is identical to your original code
1848
+ // This logic is IDENTICAL to your original code.
1855
1849
  const virtualState = useMemo(() => {
1856
1850
  const start = Math.max(0, range.startIndex);
1857
1851
  const end = Math.min(totalCount, range.endIndex);
@@ -1866,55 +1860,38 @@ function createProxyHandler<T>(
1866
1860
  });
1867
1861
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1868
1862
 
1869
- // --- STABLE SCROLL LOGIC ---
1870
- // useLayoutEffect runs after DOM mutations but before the browser paints.
1871
- // This is the perfect place to correct scroll positions.
1863
+ // This useLayoutEffect is from your original code.
1872
1864
  useLayoutEffect(() => {
1873
1865
  const container = containerRef.current;
1874
1866
  if (!container) return;
1875
1867
 
1876
- // If the list grew, we need to adjust the scroll position
1877
- // to prevent the content from jumping.
1878
- if (listGrewRef.current) {
1879
- listGrewRef.current = false; // Reset the flag
1880
-
1881
- if (isAtBottomRef.current) {
1882
- // If we were at the bottom, stay at the bottom.
1883
- // This is the fix for the auto-scroll issue.
1884
- container.scrollTop = container.scrollHeight;
1885
- } else {
1886
- // If we were in the middle, restore the previous scroll position
1887
- // plus the height of the content that was added above us.
1888
- // This is an advanced case, but for now, let's keep it simple
1889
- // as most use-cases are for chat-like views. For a simple list,
1890
- // just staying at the bottom is the main goal.
1891
- }
1892
- }
1893
- }, [totalHeight]); // This effect runs whenever the total height changes
1894
-
1895
- useEffect(() => {
1896
- const container = containerRef.current;
1897
- if (!container) return;
1898
-
1899
- // Track the previous total count to detect when new items are added
1900
- let previousTotalCount = totalCount;
1868
+ const wasAtBottom = isAtBottomRef.current;
1869
+ const listGrew = totalCount > previousTotalCountRef.current;
1870
+ previousTotalCountRef.current = totalCount;
1901
1871
 
1902
1872
  const handleScroll = () => {
1903
- if (!container) return;
1904
1873
  const { scrollTop, clientHeight, scrollHeight } = container;
1905
- // Update "is at bottom" status on every scroll
1906
1874
  isAtBottomRef.current =
1907
1875
  scrollHeight - scrollTop - clientHeight < 10;
1908
- scrollOffsetRef.current = scrollTop;
1909
-
1910
- // Find start/end indices based on positions
1911
- let startIndex = 0;
1912
- for (let i = 0; i < positions.length; i++) {
1913
- if (positions[i]! >= scrollTop) {
1914
- startIndex = i;
1915
- break;
1876
+
1877
+ // --- THE ROBUST FIX: Binary search to find the start index ---
1878
+ // This is extremely fast and correctly handles all scroll positions.
1879
+ let search = (list: number[], value: number) => {
1880
+ let low = 0;
1881
+ let high = list.length - 1;
1882
+ while (low <= high) {
1883
+ const mid = Math.floor((low + high) / 2);
1884
+ const midValue = list[mid]!;
1885
+ if (midValue < value) {
1886
+ low = mid + 1;
1887
+ } else {
1888
+ high = mid - 1;
1889
+ }
1916
1890
  }
1917
- }
1891
+ return low;
1892
+ };
1893
+
1894
+ let startIndex = search(positions, scrollTop);
1918
1895
 
1919
1896
  let endIndex = startIndex;
1920
1897
  while (
@@ -1926,7 +1903,7 @@ function createProxyHandler<T>(
1926
1903
 
1927
1904
  startIndex = Math.max(0, startIndex - overscan);
1928
1905
  endIndex = Math.min(totalCount, endIndex + overscan);
1929
-
1906
+ console.log("startIndex", startIndex, "endIndex", endIndex);
1930
1907
  setRange((prevRange) => {
1931
1908
  if (
1932
1909
  prevRange.startIndex !== startIndex ||
@@ -1938,20 +1915,33 @@ function createProxyHandler<T>(
1938
1915
  });
1939
1916
  };
1940
1917
 
1941
- // Check if the list has grown *before* the next render cycle
1942
- if (totalCount > previousTotalCount) {
1943
- listGrewRef.current = true;
1944
- }
1945
- previousTotalCount = totalCount;
1946
-
1947
1918
  container.addEventListener("scroll", handleScroll, {
1948
1919
  passive: true,
1949
1920
  });
1950
- handleScroll(); // Initial calculation
1921
+
1922
+ // This stickToBottom logic is IDENTICAL to your original.
1923
+ if (stickToBottom) {
1924
+ if (isInitialMountRef.current) {
1925
+ container.scrollTo({
1926
+ top: container.scrollHeight,
1927
+ behavior: "auto",
1928
+ });
1929
+ } else if (wasAtBottom && listGrew) {
1930
+ requestAnimationFrame(() => {
1931
+ container.scrollTo({
1932
+ top: container.scrollHeight,
1933
+ behavior: "smooth",
1934
+ });
1935
+ });
1936
+ }
1937
+ }
1938
+
1939
+ isInitialMountRef.current = false;
1940
+ handleScroll();
1951
1941
 
1952
1942
  return () =>
1953
1943
  container.removeEventListener("scroll", handleScroll);
1954
- }, [totalCount, overscan, positions]); // Depend on positions to re-run scroll logic
1944
+ }, [totalCount, overscan, stickToBottom, positions]);
1955
1945
 
1956
1946
  const scrollToBottom = useCallback(
1957
1947
  (behavior: ScrollBehavior = "smooth") => {
@@ -1967,9 +1957,9 @@ function createProxyHandler<T>(
1967
1957
 
1968
1958
  const scrollToIndex = useCallback(
1969
1959
  (index: number, behavior: ScrollBehavior = "smooth") => {
1970
- if (containerRef.current && positions[index] !== undefined) {
1960
+ if (containerRef.current) {
1971
1961
  containerRef.current.scrollTo({
1972
- top: positions[index],
1962
+ top: positions[index] || 0,
1973
1963
  behavior,
1974
1964
  });
1975
1965
  }