cogsbox-state 0.5.347 → 0.5.349

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.347",
3
+ "version": "0.5.349",
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
@@ -1813,7 +1813,12 @@ function createProxyHandler<T>(
1813
1813
  startIndex: 0,
1814
1814
  endIndex: 10,
1815
1815
  });
1816
+ const sourceArray = getGlobalStore().getNestedState(
1817
+ stateKey,
1818
+ path
1819
+ ) as any[];
1816
1820
  const isLockedToBottomRef = useRef(stickToBottom);
1821
+ const prevTotalCountRef = useRef(sourceArray.length);
1817
1822
 
1818
1823
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1819
1824
 
@@ -1826,10 +1831,6 @@ function createProxyHandler<T>(
1826
1831
  return unsubscribe;
1827
1832
  }, [stateKey]);
1828
1833
 
1829
- const sourceArray = getGlobalStore().getNestedState(
1830
- stateKey,
1831
- path
1832
- ) as any[];
1833
1834
  const totalCount = sourceArray.length;
1834
1835
 
1835
1836
  const { totalHeight, positions } = useMemo(() => {
@@ -1867,67 +1868,14 @@ function createProxyHandler<T>(
1867
1868
  });
1868
1869
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1869
1870
 
1870
- // --- YOUR SCROLLING ALGORITHM (UNCHANGED and WORKING) ---
1871
- // This effect is the entry point. It triggers when new items are added.
1871
+ // The one and only layout effect.
1872
1872
  useLayoutEffect(() => {
1873
- const container = containerRef.current;
1874
- // Only run if we are supposed to be at the bottom.
1875
- if (
1876
- !container ||
1877
- !isLockedToBottomRef.current ||
1878
- totalCount === 0
1879
- ) {
1880
- return;
1881
- }
1882
-
1883
- // STEP 1: Set the range to the end so the last items are rendered.
1884
- const visibleCount = 10;
1885
- setRange({
1886
- startIndex: Math.max(0, totalCount - visibleCount - overscan),
1887
- endIndex: totalCount,
1888
- });
1889
-
1890
- // STEP 2: Start the LOOP.
1891
- let loopCount = 0;
1892
- const intervalId = setInterval(() => {
1893
- loopCount++;
1894
- // The Check: Get the last item's height FROM THE SHADOW OBJECT.
1895
- const lastItemIndex = totalCount - 1;
1896
- const shadowArray =
1897
- getGlobalStore
1898
- .getState()
1899
- .getShadowMetadata(stateKey, path) || [];
1900
- const lastItemHeight =
1901
- shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
1902
-
1903
- if (lastItemHeight > 0) {
1904
- // EXIT CONDITION MET
1905
- clearInterval(intervalId); // Stop the loop.
1906
-
1907
- // STEP 3: Scroll.
1908
- container.scrollTo({
1909
- top: container.scrollHeight,
1910
- behavior: "smooth",
1911
- });
1912
- } else {
1913
- if (loopCount > 20) {
1914
- // Safety break
1915
- clearInterval(intervalId);
1916
- }
1917
- }
1918
- }, 100);
1919
-
1920
- // Cleanup: Stop the loop if the component unmounts.
1921
- return () => clearInterval(intervalId);
1922
- }, [totalCount]); // This whole process triggers ONLY when totalCount changes.
1923
-
1924
- // --- THE FIX IS HERE ---
1925
- // This effect now correctly handles user scrolling AND updates the view.
1926
- useEffect(() => {
1927
1873
  const container = containerRef.current;
1928
1874
  if (!container) return;
1929
1875
 
1930
- // This function now always has the LATEST totalCount and positions.
1876
+ const hasNewItems = totalCount > prevTotalCountRef.current;
1877
+
1878
+ // This function is now ALWAYS fresh.
1931
1879
  const updateVirtualRange = () => {
1932
1880
  const { scrollTop, clientHeight } = container;
1933
1881
  let low = 0,
@@ -1952,6 +1900,39 @@ function createProxyHandler<T>(
1952
1900
  });
1953
1901
  };
1954
1902
 
1903
+ // --- YOUR SCROLLING LOGIC ---
1904
+ // It only runs if we have new items and are locked to the bottom.
1905
+ if (hasNewItems && isLockedToBottomRef.current) {
1906
+ // STEP 1: Set range to the end to start measuring.
1907
+ setRange({
1908
+ startIndex: Math.max(0, totalCount - 10 - overscan),
1909
+ endIndex: totalCount,
1910
+ });
1911
+
1912
+ // STEP 2: Start the LOOP.
1913
+ const intervalId = setInterval(() => {
1914
+ const lastItemIndex = totalCount - 1;
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
+ clearInterval(intervalId);
1924
+ container.scrollTo({
1925
+ top: container.scrollHeight,
1926
+ behavior: "smooth",
1927
+ });
1928
+ }
1929
+ }, 100);
1930
+
1931
+ // This return is the cleanup for the if-block.
1932
+ return () => clearInterval(intervalId);
1933
+ }
1934
+
1935
+ // --- USER SCROLL HANDLING ---
1955
1936
  const handleUserScroll = () => {
1956
1937
  const isAtBottom =
1957
1938
  container.scrollHeight -
@@ -1961,20 +1942,23 @@ function createProxyHandler<T>(
1961
1942
  if (!isAtBottom) {
1962
1943
  isLockedToBottomRef.current = false;
1963
1944
  }
1964
- // This always calls the fresh version of updateVirtualRange.
1965
1945
  updateVirtualRange();
1966
1946
  };
1967
1947
 
1968
1948
  container.addEventListener("scroll", handleUserScroll, {
1969
1949
  passive: true,
1970
1950
  });
1971
- updateVirtualRange(); // Update range on initial load and when data changes.
1951
+ updateVirtualRange(); // Always update range for current view.
1972
1952
 
1973
- // This cleanup is crucial. It removes the old listener before adding a new one.
1953
+ // This return is the cleanup for the whole effect.
1974
1954
  return () =>
1975
1955
  container.removeEventListener("scroll", handleUserScroll);
1976
- }, [totalCount, positions]); // Its dependency array now includes totalCount and positions.
1956
+ }, [totalCount, positions]); // Re-run when layout-related data changes.
1977
1957
 
1958
+ // This simple effect tracks the item count for the next render.
1959
+ useEffect(() => {
1960
+ prevTotalCountRef.current = totalCount;
1961
+ });
1978
1962
  const scrollToBottom = useCallback(
1979
1963
  (behavior: ScrollBehavior = "smooth") => {
1980
1964
  if (containerRef.current) {