cogsbox-state 0.5.360 → 0.5.361

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.361",
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);
1894
1924
 
1895
- const shouldStartLoop =
1896
- isLockedToBottomRef.current && (hasNewItems || depsChanged);
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);
1942
+
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;
@@ -1920,66 +1977,15 @@ function createProxyHandler<T>(
1920
1977
  });
1921
1978
  };
1922
1979
 
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
- }
1939
-
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
- }
1968
-
1969
1980
  const handleUserScroll = () => {
1970
- // If our code is scrolling, ignore this event.
1971
1981
  if (isAutoScrolling.current) return;
1972
-
1973
1982
  const isAtBottom =
1974
1983
  container.scrollHeight -
1975
1984
  container.scrollTop -
1976
1985
  container.clientHeight <
1977
1986
  1;
1978
- if (!isAtBottom && isLockedToBottomRef.current) {
1979
- console.log("USER SCROLL: Lock broken.");
1987
+ if (!isAtBottom) {
1980
1988
  isLockedToBottomRef.current = false;
1981
- // If a loop was somehow running, kill it.
1982
- if (intervalId) clearInterval(intervalId);
1983
1989
  }
1984
1990
  updateVirtualRange();
1985
1991
  };
@@ -1987,23 +1993,16 @@ function createProxyHandler<T>(
1987
1993
  container.addEventListener("scroll", handleUserScroll, {
1988
1994
  passive: true,
1989
1995
  });
1996
+ updateVirtualRange();
1990
1997
 
1991
- // Update refs for the next render.
1992
- prevDepsRef.current = dependencies;
1993
- prevTotalCountRef.current = totalCount;
1994
-
1995
- return () => {
1998
+ return () =>
1996
1999
  container.removeEventListener("scroll", handleUserScroll);
1997
- if (intervalId) clearInterval(intervalId);
1998
- };
1999
- }, [totalCount, positions, ...dependencies]);
2000
+ }, [...dependencies]);
2000
2001
 
2001
2002
  const scrollToBottom = useCallback(
2002
2003
  (behavior: ScrollBehavior = "smooth") => {
2003
2004
  if (containerRef.current) {
2004
2005
  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
2006
  containerRef.current.scrollTo({
2008
2007
  top: containerRef.current.scrollHeight,
2009
2008
  behavior,
@@ -2017,7 +2016,6 @@ function createProxyHandler<T>(
2017
2016
  (index: number, behavior: ScrollBehavior = "smooth") => {
2018
2017
  if (containerRef.current && positions[index] !== undefined) {
2019
2018
  isLockedToBottomRef.current = false;
2020
- console.log("USER ACTION: Scroll lock DISABLED.");
2021
2019
  containerRef.current.scrollTo({
2022
2020
  top: positions[index],
2023
2021
  behavior,