cogsbox-state 0.5.357 → 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.357",
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
@@ -1816,7 +1815,9 @@ function createProxyHandler<T>(
1816
1815
  endIndex: 10,
1817
1816
  });
1818
1817
  const isLockedToBottomRef = useRef(stickToBottom);
1819
- const prevTotalCountRef = useRef(0);
1818
+
1819
+ // This ref will hold the ID of our loop so we can stop it.
1820
+ const scrollLoopId = useRef<NodeJS.Timeout | null>(null);
1820
1821
 
1821
1822
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1822
1823
 
@@ -1857,6 +1858,7 @@ function createProxyHandler<T>(
1857
1858
  ]);
1858
1859
 
1859
1860
  const virtualState = useMemo(() => {
1861
+ // ... same as before ...
1860
1862
  const start = Math.max(0, range.startIndex);
1861
1863
  const end = Math.min(totalCount, range.endIndex);
1862
1864
  const validIndices = Array.from(
@@ -1870,118 +1872,129 @@ function createProxyHandler<T>(
1870
1872
  });
1871
1873
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1872
1874
 
1873
- // The SINGLE effect for all layout logic.
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
- if (!container) return;
1879
+ if (
1880
+ !container ||
1881
+ !stickToBottom ||
1882
+ !isLockedToBottomRef.current
1883
+ ) {
1884
+ return;
1885
+ }
1877
1886
 
1878
- const hasNewItems = totalCount > prevTotalCountRef.current;
1879
- const shouldAutoScroll =
1880
- stickToBottom && isLockedToBottomRef.current && hasNewItems;
1881
-
1882
- // This function is for manual scrolling.
1883
- const updateVirtualRangeForUser = () => {
1884
- const { scrollTop, clientHeight } = container;
1885
- let low = 0,
1886
- high = totalCount - 1;
1887
- while (low <= high) {
1888
- const mid = Math.floor((low + high) / 2);
1889
- if (positions[mid]! < scrollTop) low = mid + 1;
1890
- else high = mid - 1;
1887
+ // If a loop is already running, stop it before starting a new one.
1888
+ if (scrollLoopId.current) {
1889
+ clearInterval(scrollLoopId.current);
1890
+ }
1891
+
1892
+ console.log("ALGORITHM: Starting...");
1893
+
1894
+ // STEP 1: Set range to render the last item.
1895
+ setRange({
1896
+ startIndex: Math.max(0, totalCount - 10 - overscan),
1897
+ endIndex: totalCount,
1898
+ });
1899
+
1900
+ // STEP 2: Start the LOOP.
1901
+ console.log(
1902
+ "ALGORITHM: Starting LOOP to wait for measurement."
1903
+ );
1904
+ let loopCount = 0;
1905
+ scrollLoopId.current = setInterval(() => {
1906
+ loopCount++;
1907
+ console.log(`LOOP ${loopCount}: Checking last item...`);
1908
+
1909
+ const lastItemIndex = totalCount - 1;
1910
+ if (lastItemIndex < 0) {
1911
+ clearInterval(scrollLoopId.current!);
1912
+ return;
1891
1913
  }
1892
- const startIndex = Math.max(0, high - overscan);
1893
- let endIndex = startIndex;
1894
- const visibleEnd = scrollTop + clientHeight;
1895
- while (
1896
- endIndex < totalCount &&
1897
- positions[endIndex]! < visibleEnd
1898
- ) {
1899
- endIndex++;
1914
+
1915
+ const shadowArray =
1916
+ getGlobalStore
1917
+ .getState()
1918
+ .getShadowMetadata(stateKey, path) || [];
1919
+ const lastItemHeight =
1920
+ shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
1921
+
1922
+ if (lastItemHeight > 0) {
1923
+ console.log(
1924
+ `%cSUCCESS: Last item is measured. Scrolling.`,
1925
+ "color: green; font-weight: bold;"
1926
+ );
1927
+ clearInterval(scrollLoopId.current!);
1928
+ scrollLoopId.current = null;
1929
+
1930
+ container.scrollTo({
1931
+ top: container.scrollHeight,
1932
+ behavior: "smooth",
1933
+ });
1934
+ } else {
1935
+ console.log("...WAITING. Height is not ready.");
1936
+ if (loopCount > 30) {
1937
+ console.error("LOOP TIMEOUT. Stopping.");
1938
+ clearInterval(scrollLoopId.current!);
1939
+ scrollLoopId.current = null;
1940
+ }
1900
1941
  }
1901
- setRange({
1902
- startIndex,
1903
- endIndex: Math.min(totalCount, endIndex + overscan),
1904
- });
1905
- };
1942
+ }, 100);
1906
1943
 
1907
- let intervalId: NodeJS.Timeout | undefined;
1944
+ return () => {
1945
+ if (scrollLoopId.current) {
1946
+ clearInterval(scrollLoopId.current);
1947
+ }
1948
+ };
1949
+ }, [totalCount, ...dependencies]);
1908
1950
 
1909
- if (shouldAutoScroll) {
1910
- // --- YOUR ALGORITHM PATH ---
1911
- console.log("ALGORITHM: Auto-scroll triggered.");
1951
+ // --- EFFECT #2: USER INTERACTION & RESET ---
1952
+ // This handles manual scrolling and resetting when the chat changes.
1953
+ useEffect(() => {
1954
+ const container = containerRef.current;
1955
+ if (!container) return;
1912
1956
 
1913
- // STEP 1: LOAD THE LAST ITEM INTO THE RANGE.
1914
- console.log(
1915
- "...Setting range to the end to render last item."
1916
- );
1917
- setRange({
1918
- startIndex: Math.max(0, totalCount - 10 - overscan),
1919
- endIndex: totalCount,
1920
- });
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;
1921
1963
 
1922
- // STEP 2: START THE LOOP.
1923
- console.log("...Starting loop to wait for measurement.");
1924
- intervalId = setInterval(() => {
1925
- const lastItemIndex = totalCount - 1;
1926
- const shadowArray =
1927
- getGlobalStore
1928
- .getState()
1929
- .getShadowMetadata(stateKey, path) || [];
1930
- const lastItemHeight =
1931
- shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
1932
-
1933
- if (lastItemHeight > 0) {
1934
- console.log(
1935
- "%c...SUCCESS: Last item measured. Scrolling.",
1936
- "color: green; font-weight: bold;"
1937
- );
1938
- clearInterval(intervalId);
1939
- container.scrollTo({
1940
- top: container.scrollHeight,
1941
- behavior: "smooth",
1942
- });
1943
- } else {
1944
- console.log("...WAITING for measurement.");
1945
- }
1946
- }, 100);
1947
- } else {
1948
- // --- MANUAL SCROLL PATH ---
1949
- // If we are not auto-scrolling, just update the view for the user's position.
1950
- updateVirtualRangeForUser();
1951
- }
1964
+ const updateVirtualRange = () => {
1965
+ /* ... same as before ... */
1966
+ };
1952
1967
 
1953
- // --- USER INTERACTION ---
1954
1968
  const handleUserScroll = () => {
1955
- // If the user scrolls up, break the lock.
1956
1969
  const isAtBottom =
1957
1970
  container.scrollHeight -
1958
1971
  container.scrollTop -
1959
1972
  container.clientHeight <
1960
1973
  1;
1961
1974
  if (!isAtBottom) {
1962
- isLockedToBottomRef.current = false;
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
+ }
1963
1985
  }
1964
- // And update the view to where they scrolled.
1965
- updateVirtualRangeForUser();
1986
+ updateVirtualRange();
1966
1987
  };
1967
1988
 
1968
1989
  container.addEventListener("scroll", handleUserScroll, {
1969
1990
  passive: true,
1970
1991
  });
1992
+ updateVirtualRange();
1971
1993
 
1972
- return () => {
1994
+ return () =>
1973
1995
  container.removeEventListener("scroll", handleUserScroll);
1974
- if (intervalId) {
1975
- clearInterval(intervalId);
1976
- }
1977
- };
1978
1996
  }, [totalCount, positions, ...dependencies]);
1979
1997
 
1980
- // This simple effect tracks the item count for the next render.
1981
- useEffect(() => {
1982
- prevTotalCountRef.current = totalCount;
1983
- });
1984
-
1985
1998
  const scrollToBottom = useCallback(
1986
1999
  (behavior: ScrollBehavior = "smooth") => {
1987
2000
  if (containerRef.current) {