cogsbox-state 0.5.356 → 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.356",
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,85 +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
- return () => {
1940
- console.log("ALGORITHM: Cleaning up loop.");
1941
- clearInterval(intervalId);
1942
- };
1943
- }, [totalCount, totalHeight, ...(options.dependencies ?? [])]); // This whole process triggers ONLY when totalCount changes.
1944
-
1945
- // Effect to handle user scrolling.
1946
- useEffect(() => {
1947
1875
  const container = containerRef.current;
1948
1876
  if (!container) return;
1949
1877
 
1950
- 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 = () => {
1951
1884
  const { scrollTop, clientHeight } = container;
1952
1885
  let low = 0,
1953
1886
  high = totalCount - 1;
@@ -1971,11 +1904,55 @@ function createProxyHandler<T>(
1971
1904
  });
1972
1905
  };
1973
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 ---
1974
1954
  const handleUserScroll = () => {
1975
- if (isAutoScrolling.current) {
1976
- // <--- ADD THIS CHECK
1977
- return;
1978
- }
1955
+ // If the user scrolls up, break the lock.
1979
1956
  const isAtBottom =
1980
1957
  container.scrollHeight -
1981
1958
  container.scrollTop -
@@ -1983,20 +1960,27 @@ function createProxyHandler<T>(
1983
1960
  1;
1984
1961
  if (!isAtBottom) {
1985
1962
  isLockedToBottomRef.current = false;
1986
- console.log("USER ACTION: Scroll lock DISABLED.");
1987
1963
  }
1988
- updateVirtualRange();
1964
+ // And update the view to where they scrolled.
1965
+ updateVirtualRangeForUser();
1989
1966
  };
1990
1967
 
1991
1968
  container.addEventListener("scroll", handleUserScroll, {
1992
1969
  passive: true,
1993
1970
  });
1994
- // Always run on mount and when data changes to show correct initial view
1995
- updateVirtualRange();
1996
1971
 
1997
- return () =>
1972
+ return () => {
1998
1973
  container.removeEventListener("scroll", handleUserScroll);
1999
- }, [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
+ });
2000
1984
 
2001
1985
  const scrollToBottom = useCallback(
2002
1986
  (behavior: ScrollBehavior = "smooth") => {