cogsbox-state 0.5.360 → 0.5.362

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.360",
3
+ "version": "0.5.362",
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
@@ -1815,11 +1815,7 @@ function createProxyHandler<T>(
1815
1815
  endIndex: 10,
1816
1816
  });
1817
1817
  const isLockedToBottomRef = useRef(stickToBottom);
1818
-
1819
- // This flag prevents our own scroll animation from breaking the lock.
1820
1818
  const isAutoScrolling = useRef(false);
1821
-
1822
- const prevDepsRef = useRef(dependencies);
1823
1819
  const prevTotalCountRef = useRef(0);
1824
1820
 
1825
1821
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
@@ -1840,7 +1836,6 @@ function createProxyHandler<T>(
1840
1836
  const totalCount = sourceArray.length;
1841
1837
 
1842
1838
  const { totalHeight, positions } = useMemo(() => {
1843
- // ... same as before ...
1844
1839
  const shadowArray =
1845
1840
  getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
1846
1841
  [];
@@ -1862,7 +1857,6 @@ function createProxyHandler<T>(
1862
1857
  ]);
1863
1858
 
1864
1859
  const virtualState = useMemo(() => {
1865
- // ... same as before ...
1866
1860
  const start = Math.max(0, range.startIndex);
1867
1861
  const end = Math.min(totalCount, range.endIndex);
1868
1862
  const validIndices = Array.from(
@@ -1876,27 +1870,90 @@ function createProxyHandler<T>(
1876
1870
  });
1877
1871
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1878
1872
 
1879
- // The single, authoritative effect for ALL layout logic.
1873
+ // --- PHASE 1: Detect auto-scroll need and SET THE RANGE ---
1874
+ useLayoutEffect(() => {
1875
+ const hasNewItems = totalCount > prevTotalCountRef.current;
1876
+ if (isLockedToBottomRef.current && hasNewItems) {
1877
+ console.log(
1878
+ "PHASE 1: Auto-scroll needed. Setting range to render the last item."
1879
+ );
1880
+ setRange({
1881
+ startIndex: Math.max(0, totalCount - 10 - overscan),
1882
+ endIndex: totalCount,
1883
+ });
1884
+ }
1885
+ prevTotalCountRef.current = totalCount;
1886
+ }, [totalCount]);
1887
+
1888
+ // --- PHASE 2: Wait for measurement and SCROLL ---
1880
1889
  useLayoutEffect(() => {
1881
1890
  const container = containerRef.current;
1882
- if (!container) return;
1891
+ const isRangeAtEnd =
1892
+ range.endIndex === totalCount && totalCount > 0;
1883
1893
 
1884
- const depsChanged = !isDeepEqual(
1885
- dependencies,
1886
- prevDepsRef.current
1894
+ if (
1895
+ !container ||
1896
+ !isLockedToBottomRef.current ||
1897
+ !isRangeAtEnd
1898
+ ) {
1899
+ return;
1900
+ }
1901
+
1902
+ console.log(
1903
+ "PHASE 2: Range is at the end. Starting the measurement loop."
1887
1904
  );
1888
- const hasNewItems = totalCount > prevTotalCountRef.current;
1905
+ let loopCount = 0;
1906
+ const intervalId = setInterval(() => {
1907
+ loopCount++;
1908
+ console.log(`LOOP ${loopCount}: Checking last item...`);
1889
1909
 
1890
- if (depsChanged) {
1891
- console.log("DEPENDENCY CHANGE: Resetting scroll lock.");
1892
- isLockedToBottomRef.current = stickToBottom;
1893
- }
1910
+ const lastItemIndex = totalCount - 1;
1911
+ const shadowArray =
1912
+ getGlobalStore
1913
+ .getState()
1914
+ .getShadowMetadata(stateKey, path) || [];
1915
+ const lastItemHeight =
1916
+ shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
1917
+
1918
+ if (lastItemHeight > 0) {
1919
+ console.log(
1920
+ `%cSUCCESS: Last item height is ${lastItemHeight}. Scrolling now.`,
1921
+ "color: green; font-weight: bold;"
1922
+ );
1923
+ clearInterval(intervalId);
1924
+
1925
+ isAutoScrolling.current = true;
1926
+ container.scrollTo({
1927
+ top: container.scrollHeight,
1928
+ behavior: "smooth",
1929
+ });
1930
+ setTimeout(() => {
1931
+ isAutoScrolling.current = false;
1932
+ }, 1000);
1933
+ } else if (loopCount > 20) {
1934
+ console.error(
1935
+ "LOOP TIMEOUT: Last item was never measured. Stopping loop."
1936
+ );
1937
+ clearInterval(intervalId);
1938
+ } else {
1939
+ console.log("...WAITING. Height is not ready.");
1940
+ }
1941
+ }, 100);
1894
1942
 
1895
- const shouldStartLoop =
1896
- isLockedToBottomRef.current && (hasNewItems || depsChanged);
1943
+ return () => clearInterval(intervalId);
1944
+ }, [range.endIndex, totalCount, positions]);
1945
+
1946
+ // --- PHASE 3: Handle User Interaction and Resets ---
1947
+ useEffect(() => {
1948
+ const container = containerRef.current;
1949
+ if (!container) return;
1950
+
1951
+ console.log(
1952
+ "DEPENDENCY CHANGE: Resetting scroll lock and initial view."
1953
+ );
1954
+ isLockedToBottomRef.current = stickToBottom;
1897
1955
 
1898
1956
  const updateVirtualRange = () => {
1899
- // This is the full, non-placeholder function.
1900
1957
  const { scrollTop, clientHeight } = container;
1901
1958
  let low = 0,
1902
1959
  high = totalCount - 1;
@@ -1914,72 +1971,25 @@ function createProxyHandler<T>(
1914
1971
  ) {
1915
1972
  endIndex++;
1916
1973
  }
1917
- setRange({
1918
- startIndex,
1919
- endIndex: Math.min(totalCount, endIndex + overscan),
1920
- });
1921
- };
1974
+ const newEndIndex = Math.min(totalCount, endIndex + overscan);
1922
1975
 
1923
- let intervalId: NodeJS.Timeout | undefined;
1924
-
1925
- if (shouldStartLoop) {
1926
- // --- YOUR ALGORITHM ---
1927
- console.log("ALGORITHM: Starting...");
1928
- setRange({
1929
- startIndex: Math.max(0, totalCount - 10 - overscan),
1930
- endIndex: totalCount,
1931
- });
1932
-
1933
- intervalId = setInterval(() => {
1934
- const lastItemIndex = totalCount - 1;
1935
- if (lastItemIndex < 0) {
1936
- clearInterval(intervalId);
1937
- return;
1938
- }
1976
+ // --- LOGGING ADDED HERE ---
1977
+ console.log(
1978
+ `RANGE UPDATE: Start: ${startIndex}, End: ${newEndIndex}, Total: ${totalCount}`
1979
+ );
1939
1980
 
1940
- const shadowArray =
1941
- getGlobalStore
1942
- .getState()
1943
- .getShadowMetadata(stateKey, path) || [];
1944
- const lastItemHeight =
1945
- shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
1946
-
1947
- if (lastItemHeight > 0) {
1948
- clearInterval(intervalId);
1949
- console.log("%cSUCCESS: Scrolling now.", "color: green;");
1950
-
1951
- // Set the flag to true before we start our animation.
1952
- isAutoScrolling.current = true;
1953
-
1954
- container.scrollTo({
1955
- top: container.scrollHeight,
1956
- behavior: "smooth",
1957
- });
1958
-
1959
- // After 1 second, assume animation is done and unset the flag.
1960
- setTimeout(() => {
1961
- isAutoScrolling.current = false;
1962
- }, 1000);
1963
- }
1964
- }, 100);
1965
- } else {
1966
- updateVirtualRange();
1967
- }
1981
+ setRange({ startIndex, endIndex: newEndIndex });
1982
+ };
1968
1983
 
1969
1984
  const handleUserScroll = () => {
1970
- // If our code is scrolling, ignore this event.
1971
1985
  if (isAutoScrolling.current) return;
1972
-
1973
1986
  const isAtBottom =
1974
1987
  container.scrollHeight -
1975
1988
  container.scrollTop -
1976
1989
  container.clientHeight <
1977
1990
  1;
1978
- if (!isAtBottom && isLockedToBottomRef.current) {
1979
- console.log("USER SCROLL: Lock broken.");
1991
+ if (!isAtBottom) {
1980
1992
  isLockedToBottomRef.current = false;
1981
- // If a loop was somehow running, kill it.
1982
- if (intervalId) clearInterval(intervalId);
1983
1993
  }
1984
1994
  updateVirtualRange();
1985
1995
  };
@@ -1987,23 +1997,16 @@ function createProxyHandler<T>(
1987
1997
  container.addEventListener("scroll", handleUserScroll, {
1988
1998
  passive: true,
1989
1999
  });
2000
+ updateVirtualRange();
1990
2001
 
1991
- // Update refs for the next render.
1992
- prevDepsRef.current = dependencies;
1993
- prevTotalCountRef.current = totalCount;
1994
-
1995
- return () => {
2002
+ return () =>
1996
2003
  container.removeEventListener("scroll", handleUserScroll);
1997
- if (intervalId) clearInterval(intervalId);
1998
- };
1999
- }, [totalCount, positions, ...dependencies]);
2004
+ }, [...dependencies, totalCount, positions]); // <--- THE FIX
2000
2005
 
2001
2006
  const scrollToBottom = useCallback(
2002
2007
  (behavior: ScrollBehavior = "smooth") => {
2003
2008
  if (containerRef.current) {
2004
2009
  isLockedToBottomRef.current = true;
2005
- console.log("USER ACTION: Scroll lock ENABLED.");
2006
- // This is a manual trigger, so we don't need the loop. Just scroll.
2007
2010
  containerRef.current.scrollTo({
2008
2011
  top: containerRef.current.scrollHeight,
2009
2012
  behavior,
@@ -2017,7 +2020,6 @@ function createProxyHandler<T>(
2017
2020
  (index: number, behavior: ScrollBehavior = "smooth") => {
2018
2021
  if (containerRef.current && positions[index] !== undefined) {
2019
2022
  isLockedToBottomRef.current = false;
2020
- console.log("USER ACTION: Scroll lock DISABLED.");
2021
2023
  containerRef.current.scrollTo({
2022
2024
  top: positions[index],
2023
2025
  behavior,