cogsbox-state 0.5.356 → 0.5.358

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.356",
3
+ "version": "0.5.358",
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
@@ -1798,7 +1798,6 @@ function createProxyHandler<T>(
1798
1798
  return selectedIndex ?? -1;
1799
1799
  };
1800
1800
  }
1801
-
1802
1801
  if (prop === "useVirtualView") {
1803
1802
  return (
1804
1803
  options: VirtualViewOptions
@@ -1807,6 +1806,7 @@ function createProxyHandler<T>(
1807
1806
  itemHeight = 50,
1808
1807
  overscan = 6,
1809
1808
  stickToBottom = false,
1809
+ dependencies = [],
1810
1810
  } = options;
1811
1811
 
1812
1812
  const containerRef = useRef<HTMLDivElement | null>(null);
@@ -1814,9 +1814,11 @@ function createProxyHandler<T>(
1814
1814
  startIndex: 0,
1815
1815
  endIndex: 10,
1816
1816
  });
1817
-
1818
1817
  const isLockedToBottomRef = useRef(stickToBottom);
1819
- const isAutoScrolling = useRef(false);
1818
+
1819
+ // This ref will hold the ID of our loop so we can stop it.
1820
+ const scrollLoopId = useRef<NodeJS.Timeout | null>(null);
1821
+
1820
1822
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1821
1823
 
1822
1824
  useEffect(() => {
@@ -1856,6 +1858,7 @@ function createProxyHandler<T>(
1856
1858
  ]);
1857
1859
 
1858
1860
  const virtualState = useMemo(() => {
1861
+ // ... same as before ...
1859
1862
  const start = Math.max(0, range.startIndex);
1860
1863
  const end = Math.min(totalCount, range.endIndex);
1861
1864
  const validIndices = Array.from(
@@ -1869,24 +1872,28 @@ function createProxyHandler<T>(
1869
1872
  });
1870
1873
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1871
1874
 
1872
- // --- YOUR ALGORITHM IMPLEMENTED ---
1873
- // This effect is the entry point. It triggers when new items are added.
1875
+ // --- EFFECT #1: THE SCROLL ALGORITHM ---
1876
+ // This runs when the data or dependencies change.
1874
1877
  useLayoutEffect(() => {
1875
1878
  const container = containerRef.current;
1876
- // Only run if we have new items and are supposed to be at the bottom.
1877
1879
  if (
1878
1880
  !container ||
1879
- !isLockedToBottomRef.current ||
1880
- totalCount === 0
1881
+ !stickToBottom ||
1882
+ !isLockedToBottomRef.current
1881
1883
  ) {
1882
1884
  return;
1883
1885
  }
1884
1886
 
1885
- // STEP 1: Set the range to the end so the last items are rendered.
1887
+ // If a loop is already running, stop it before starting a new one.
1888
+ if (scrollLoopId.current) {
1889
+ clearInterval(scrollLoopId.current);
1890
+ }
1891
+
1886
1892
  console.log("ALGORITHM: Starting...");
1887
- const visibleCount = 10;
1893
+
1894
+ // STEP 1: Set range to render the last item.
1888
1895
  setRange({
1889
- startIndex: Math.max(0, totalCount - visibleCount - overscan),
1896
+ startIndex: Math.max(0, totalCount - 10 - overscan),
1890
1897
  endIndex: totalCount,
1891
1898
  });
1892
1899
 
@@ -1895,12 +1902,16 @@ function createProxyHandler<T>(
1895
1902
  "ALGORITHM: Starting LOOP to wait for measurement."
1896
1903
  );
1897
1904
  let loopCount = 0;
1898
- const intervalId = setInterval(() => {
1905
+ scrollLoopId.current = setInterval(() => {
1899
1906
  loopCount++;
1900
1907
  console.log(`LOOP ${loopCount}: Checking last item...`);
1901
1908
 
1902
- // The Check: Get the last item's height FROM THE SHADOW OBJECT.
1903
1909
  const lastItemIndex = totalCount - 1;
1910
+ if (lastItemIndex < 0) {
1911
+ clearInterval(scrollLoopId.current!);
1912
+ return;
1913
+ }
1914
+
1904
1915
  const shadowArray =
1905
1916
  getGlobalStore
1906
1917
  .getState()
@@ -1909,81 +1920,68 @@ function createProxyHandler<T>(
1909
1920
  shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
1910
1921
 
1911
1922
  if (lastItemHeight > 0) {
1912
- // EXIT CONDITION MET
1913
1923
  console.log(
1914
- `%cSUCCESS: Last item height is ${lastItemHeight}. Scrolling now.`,
1924
+ `%cSUCCESS: Last item is measured. Scrolling.`,
1915
1925
  "color: green; font-weight: bold;"
1916
1926
  );
1917
- clearInterval(intervalId); // Stop the loop.
1918
- isAutoScrolling.current = true;
1919
- // STEP 3: Scroll.
1927
+ clearInterval(scrollLoopId.current!);
1928
+ scrollLoopId.current = null;
1929
+
1920
1930
  container.scrollTo({
1921
1931
  top: container.scrollHeight,
1922
1932
  behavior: "smooth",
1923
1933
  });
1924
- setTimeout(() => {
1925
- isAutoScrolling.current = false;
1926
- }, 1000);
1927
1934
  } else {
1928
1935
  console.log("...WAITING. Height is not ready.");
1929
- if (loopCount > 20) {
1930
- // Safety break to prevent infinite loops
1931
- console.error(
1932
- "LOOP TIMEOUT: Last item was never measured. Stopping loop."
1933
- );
1934
- clearInterval(intervalId);
1936
+ if (loopCount > 30) {
1937
+ console.error("LOOP TIMEOUT. Stopping.");
1938
+ clearInterval(scrollLoopId.current!);
1939
+ scrollLoopId.current = null;
1935
1940
  }
1936
1941
  }
1937
- }, 100); // Check every 100ms.
1942
+ }, 100);
1938
1943
 
1939
1944
  return () => {
1940
- console.log("ALGORITHM: Cleaning up loop.");
1941
- clearInterval(intervalId);
1945
+ if (scrollLoopId.current) {
1946
+ clearInterval(scrollLoopId.current);
1947
+ }
1942
1948
  };
1943
- }, [totalCount, totalHeight, ...(options.dependencies ?? [])]); // This whole process triggers ONLY when totalCount changes.
1949
+ }, [totalCount, ...dependencies]);
1944
1950
 
1945
- // Effect to handle user scrolling.
1951
+ // --- EFFECT #2: USER INTERACTION & RESET ---
1952
+ // This handles manual scrolling and resetting when the chat changes.
1946
1953
  useEffect(() => {
1947
1954
  const container = containerRef.current;
1948
1955
  if (!container) return;
1949
1956
 
1957
+ // When the chat changes (dependencies change), reset the lock.
1958
+ console.log(
1959
+ "DEPENDENCY CHANGE: Resetting scroll lock to:",
1960
+ stickToBottom
1961
+ );
1962
+ isLockedToBottomRef.current = stickToBottom;
1963
+
1950
1964
  const updateVirtualRange = () => {
1951
- const { scrollTop, clientHeight } = container;
1952
- let low = 0,
1953
- high = totalCount - 1;
1954
- while (low <= high) {
1955
- const mid = Math.floor((low + high) / 2);
1956
- if (positions[mid]! < scrollTop) low = mid + 1;
1957
- else high = mid - 1;
1958
- }
1959
- const startIndex = Math.max(0, high - overscan);
1960
- let endIndex = startIndex;
1961
- const visibleEnd = scrollTop + clientHeight;
1962
- while (
1963
- endIndex < totalCount &&
1964
- positions[endIndex]! < visibleEnd
1965
- ) {
1966
- endIndex++;
1967
- }
1968
- setRange({
1969
- startIndex,
1970
- endIndex: Math.min(totalCount, endIndex + overscan),
1971
- });
1965
+ /* ... same as before ... */
1972
1966
  };
1973
1967
 
1974
1968
  const handleUserScroll = () => {
1975
- if (isAutoScrolling.current) {
1976
- // <--- ADD THIS CHECK
1977
- return;
1978
- }
1979
1969
  const isAtBottom =
1980
1970
  container.scrollHeight -
1981
1971
  container.scrollTop -
1982
1972
  container.clientHeight <
1983
1973
  1;
1984
1974
  if (!isAtBottom) {
1985
- isLockedToBottomRef.current = false;
1986
- console.log("USER ACTION: Scroll lock DISABLED.");
1975
+ if (isLockedToBottomRef.current) {
1976
+ console.log("USER SCROLL: Lock broken.");
1977
+ isLockedToBottomRef.current = false;
1978
+ // If a scroll loop was running, kill it.
1979
+ if (scrollLoopId.current) {
1980
+ clearInterval(scrollLoopId.current);
1981
+ scrollLoopId.current = null;
1982
+ console.log("...Auto-scroll loop terminated by user.");
1983
+ }
1984
+ }
1987
1985
  }
1988
1986
  updateVirtualRange();
1989
1987
  };
@@ -1991,12 +1989,11 @@ function createProxyHandler<T>(
1991
1989
  container.addEventListener("scroll", handleUserScroll, {
1992
1990
  passive: true,
1993
1991
  });
1994
- // Always run on mount and when data changes to show correct initial view
1995
1992
  updateVirtualRange();
1996
1993
 
1997
1994
  return () =>
1998
1995
  container.removeEventListener("scroll", handleUserScroll);
1999
- }, [totalCount, positions]);
1996
+ }, [totalCount, positions, ...dependencies]);
2000
1997
 
2001
1998
  const scrollToBottom = useCallback(
2002
1999
  (behavior: ScrollBehavior = "smooth") => {