cogsbox-state 0.5.345 → 0.5.347

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.345",
3
+ "version": "0.5.347",
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,29 +1807,12 @@ function createProxyHandler<T>(
1807
1807
  overscan = 6,
1808
1808
  stickToBottom = false,
1809
1809
  } = options;
1810
- const sourceArray = getGlobalStore().getNestedState(
1811
- stateKey,
1812
- path
1813
- ) as any[];
1810
+
1814
1811
  const containerRef = useRef<HTMLDivElement | null>(null);
1815
- // We'll set the range to the end first, then let an effect handle the scroll.
1816
- const initialRange = () => {
1817
- const sourceArray = getGlobalStore
1818
- .getState()
1819
- .getNestedState(stateKey, path) as any[];
1820
- if (stickToBottom) {
1821
- const visibleCount = 10; // A reasonable guess for initial render
1822
- return {
1823
- startIndex: Math.max(
1824
- 0,
1825
- sourceArray.length - visibleCount - overscan
1826
- ),
1827
- endIndex: sourceArray.length,
1828
- };
1829
- }
1830
- return { startIndex: 0, endIndex: 10 };
1831
- };
1832
- const [range, setRange] = useState(initialRange);
1812
+ const [range, setRange] = useState({
1813
+ startIndex: 0,
1814
+ endIndex: 10,
1815
+ });
1833
1816
  const isLockedToBottomRef = useRef(stickToBottom);
1834
1817
 
1835
1818
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
@@ -1843,6 +1826,10 @@ function createProxyHandler<T>(
1843
1826
  return unsubscribe;
1844
1827
  }, [stateKey]);
1845
1828
 
1829
+ const sourceArray = getGlobalStore().getNestedState(
1830
+ stateKey,
1831
+ path
1832
+ ) as any[];
1846
1833
  const totalCount = sourceArray.length;
1847
1834
 
1848
1835
  const { totalHeight, positions } = useMemo(() => {
@@ -1880,47 +1867,67 @@ function createProxyHandler<T>(
1880
1867
  });
1881
1868
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1882
1869
 
1883
- // This is the implementation of YOUR ALGORITHM.
1870
+ // --- YOUR SCROLLING ALGORITHM (UNCHANGED and WORKING) ---
1871
+ // This effect is the entry point. It triggers when new items are added.
1884
1872
  useLayoutEffect(() => {
1885
1873
  const container = containerRef.current;
1874
+ // Only run if we are supposed to be at the bottom.
1886
1875
  if (
1887
1876
  !container ||
1888
- !stickToBottom ||
1889
- !isLockedToBottomRef.current
1877
+ !isLockedToBottomRef.current ||
1878
+ totalCount === 0
1890
1879
  ) {
1891
1880
  return;
1892
1881
  }
1893
1882
 
1894
- // STEP 1: Check if the last item is measured. This is our "ready" signal.
1895
- const lastItemIndex = totalCount - 1;
1896
- const shadowArray =
1897
- getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
1898
- [];
1899
- const lastItemIsMeasured =
1900
- lastItemIndex >= 0 &&
1901
- shadowArray[lastItemIndex]?.virtualizer?.itemHeight > 0;
1902
-
1903
- // STEP 2: If it's measured, we know totalHeight is correct. We can now scroll.
1904
- if (lastItemIsMeasured || totalCount === 0) {
1905
- // A timeout is essential for 'smooth' to work reliably after a render.
1906
- const scrollTimeout = setTimeout(() => {
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.
1907
1908
  container.scrollTo({
1908
1909
  top: container.scrollHeight,
1909
1910
  behavior: "smooth",
1910
1911
  });
1911
- }, 50); // A small buffer is safer than 0ms.
1912
+ } else {
1913
+ if (loopCount > 20) {
1914
+ // Safety break
1915
+ clearInterval(intervalId);
1916
+ }
1917
+ }
1918
+ }, 100);
1912
1919
 
1913
- return () => clearTimeout(scrollTimeout);
1914
- }
1915
- // If the last item is NOT measured, this effect does nothing and simply waits.
1916
- // It will automatically re-run when the measurement comes in (via shadowUpdateTrigger).
1917
- }, [totalCount, totalHeight, stickToBottom]); // Re-run when layout changes.
1920
+ // Cleanup: Stop the loop if the component unmounts.
1921
+ return () => clearInterval(intervalId);
1922
+ }, [totalCount]); // This whole process triggers ONLY when totalCount changes.
1918
1923
 
1919
- // This effect ONLY handles user interaction and range updates.
1924
+ // --- THE FIX IS HERE ---
1925
+ // This effect now correctly handles user scrolling AND updates the view.
1920
1926
  useEffect(() => {
1921
1927
  const container = containerRef.current;
1922
1928
  if (!container) return;
1923
1929
 
1930
+ // This function now always has the LATEST totalCount and positions.
1924
1931
  const updateVirtualRange = () => {
1925
1932
  const { scrollTop, clientHeight } = container;
1926
1933
  let low = 0,
@@ -1954,18 +1961,19 @@ function createProxyHandler<T>(
1954
1961
  if (!isAtBottom) {
1955
1962
  isLockedToBottomRef.current = false;
1956
1963
  }
1964
+ // This always calls the fresh version of updateVirtualRange.
1957
1965
  updateVirtualRange();
1958
1966
  };
1959
1967
 
1960
1968
  container.addEventListener("scroll", handleUserScroll, {
1961
1969
  passive: true,
1962
1970
  });
1963
- // Initial range calculation
1964
- updateVirtualRange();
1971
+ updateVirtualRange(); // Update range on initial load and when data changes.
1965
1972
 
1973
+ // This cleanup is crucial. It removes the old listener before adding a new one.
1966
1974
  return () =>
1967
1975
  container.removeEventListener("scroll", handleUserScroll);
1968
- }, [totalCount, positions]);
1976
+ }, [totalCount, positions]); // Its dependency array now includes totalCount and positions.
1969
1977
 
1970
1978
  const scrollToBottom = useCallback(
1971
1979
  (behavior: ScrollBehavior = "smooth") => {
@@ -2010,7 +2018,6 @@ function createProxyHandler<T>(
2010
2018
  },
2011
2019
  },
2012
2020
  };
2013
-
2014
2021
  return {
2015
2022
  virtualState,
2016
2023
  virtualizerProps,