cogsbox-state 0.5.355 → 0.5.357

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.355",
3
+ "version": "0.5.357",
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
@@ -1807,6 +1807,7 @@ function createProxyHandler<T>(
1807
1807
  itemHeight = 50,
1808
1808
  overscan = 6,
1809
1809
  stickToBottom = false,
1810
+ dependencies = [],
1810
1811
  } = options;
1811
1812
 
1812
1813
  const containerRef = useRef<HTMLDivElement | null>(null);
@@ -1814,9 +1815,9 @@ function createProxyHandler<T>(
1814
1815
  startIndex: 0,
1815
1816
  endIndex: 10,
1816
1817
  });
1817
-
1818
1818
  const isLockedToBottomRef = useRef(stickToBottom);
1819
- const isAutoScrolling = useRef(false);
1819
+ const prevTotalCountRef = useRef(0);
1820
+
1820
1821
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1821
1822
 
1822
1823
  useEffect(() => {
@@ -1869,86 +1870,17 @@ function createProxyHandler<T>(
1869
1870
  });
1870
1871
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1871
1872
 
1872
- // --- YOUR ALGORITHM IMPLEMENTED ---
1873
- // This effect is the entry point. It triggers when new items are added.
1873
+ // The SINGLE effect for all layout logic.
1874
1874
  useLayoutEffect(() => {
1875
- const container = containerRef.current;
1876
- // Only run if we have new items and are supposed to be at the bottom.
1877
- if (
1878
- !container ||
1879
- !isLockedToBottomRef.current ||
1880
- totalCount === 0
1881
- ) {
1882
- return;
1883
- }
1884
-
1885
- // STEP 1: Set the range to the end so the last items are rendered.
1886
- console.log("ALGORITHM: Starting...");
1887
- const visibleCount = 10;
1888
- setRange({
1889
- startIndex: Math.max(0, totalCount - visibleCount - overscan),
1890
- endIndex: totalCount,
1891
- });
1892
-
1893
- // STEP 2: Start the LOOP.
1894
- console.log(
1895
- "ALGORITHM: Starting LOOP to wait for measurement."
1896
- );
1897
- let loopCount = 0;
1898
- const intervalId = setInterval(() => {
1899
- loopCount++;
1900
- console.log(`LOOP ${loopCount}: Checking last item...`);
1901
-
1902
- // The Check: Get the last item's height FROM THE SHADOW OBJECT.
1903
- const lastItemIndex = totalCount - 1;
1904
- const shadowArray =
1905
- getGlobalStore
1906
- .getState()
1907
- .getShadowMetadata(stateKey, path) || [];
1908
- const lastItemHeight =
1909
- shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
1910
-
1911
- if (lastItemHeight > 0) {
1912
- // EXIT CONDITION MET
1913
- console.log(
1914
- `%cSUCCESS: Last item height is ${lastItemHeight}. Scrolling now.`,
1915
- "color: green; font-weight: bold;"
1916
- );
1917
- clearInterval(intervalId); // Stop the loop.
1918
- isAutoScrolling.current = true;
1919
- // STEP 3: Scroll.
1920
- container.scrollTo({
1921
- top: container.scrollHeight,
1922
- behavior: "smooth",
1923
- });
1924
- setTimeout(() => {
1925
- isAutoScrolling.current = false;
1926
- }, 1000);
1927
- } else {
1928
- 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);
1935
- }
1936
- }
1937
- }, 100); // Check every 100ms.
1938
-
1939
- // Cleanup: Stop the loop if the component unmounts.
1940
- return () => {
1941
- console.log("ALGORITHM: Cleaning up loop.");
1942
- clearInterval(intervalId);
1943
- };
1944
- }, [totalCount, totalHeight, ...(options.dependencies ?? [])]); // This whole process triggers ONLY when totalCount changes.
1945
-
1946
- // Effect to handle user scrolling.
1947
- useEffect(() => {
1948
1875
  const container = containerRef.current;
1949
1876
  if (!container) return;
1950
1877
 
1951
- const updateVirtualRange = () => {
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 = () => {
1952
1884
  const { scrollTop, clientHeight } = container;
1953
1885
  let low = 0,
1954
1886
  high = totalCount - 1;
@@ -1972,11 +1904,55 @@ function createProxyHandler<T>(
1972
1904
  });
1973
1905
  };
1974
1906
 
1907
+ let intervalId: NodeJS.Timeout | undefined;
1908
+
1909
+ if (shouldAutoScroll) {
1910
+ // --- YOUR ALGORITHM PATH ---
1911
+ console.log("ALGORITHM: Auto-scroll triggered.");
1912
+
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
+ });
1921
+
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
+ }
1952
+
1953
+ // --- USER INTERACTION ---
1975
1954
  const handleUserScroll = () => {
1976
- if (isAutoScrolling.current) {
1977
- // <--- ADD THIS CHECK
1978
- return;
1979
- }
1955
+ // If the user scrolls up, break the lock.
1980
1956
  const isAtBottom =
1981
1957
  container.scrollHeight -
1982
1958
  container.scrollTop -
@@ -1984,20 +1960,27 @@ function createProxyHandler<T>(
1984
1960
  1;
1985
1961
  if (!isAtBottom) {
1986
1962
  isLockedToBottomRef.current = false;
1987
- console.log("USER ACTION: Scroll lock DISABLED.");
1988
1963
  }
1989
- updateVirtualRange();
1964
+ // And update the view to where they scrolled.
1965
+ updateVirtualRangeForUser();
1990
1966
  };
1991
1967
 
1992
1968
  container.addEventListener("scroll", handleUserScroll, {
1993
1969
  passive: true,
1994
1970
  });
1995
- // Always run on mount and when data changes to show correct initial view
1996
- updateVirtualRange();
1997
1971
 
1998
- return () =>
1972
+ return () => {
1999
1973
  container.removeEventListener("scroll", handleUserScroll);
2000
- }, [totalCount, positions]);
1974
+ if (intervalId) {
1975
+ clearInterval(intervalId);
1976
+ }
1977
+ };
1978
+ }, [totalCount, positions, ...dependencies]);
1979
+
1980
+ // This simple effect tracks the item count for the next render.
1981
+ useEffect(() => {
1982
+ prevTotalCountRef.current = totalCount;
1983
+ });
2001
1984
 
2002
1985
  const scrollToBottom = useCallback(
2003
1986
  (behavior: ScrollBehavior = "smooth") => {