cogsbox-state 0.5.332 → 0.5.334

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.332",
3
+ "version": "0.5.334",
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
@@ -1814,11 +1814,9 @@ function createProxyHandler<T>(
1814
1814
  endIndex: 10,
1815
1815
  });
1816
1816
 
1817
- // This ref tracks if the user is locked to the bottom.
1818
- const isLockedToBottomRef = useRef(stickToBottom);
1819
-
1820
- // This state triggers a re-render when item heights change.
1817
+ const isLockedToBottomRef = useRef(false); // Always start false
1821
1818
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1819
+ const lastTotalCountRef = useRef(0); // Track previous count
1822
1820
 
1823
1821
  useEffect(() => {
1824
1822
  const unsubscribe = getGlobalStore
@@ -1835,7 +1833,6 @@ function createProxyHandler<T>(
1835
1833
  ) as any[];
1836
1834
  const totalCount = sourceArray.length;
1837
1835
 
1838
- // Calculate heights from shadow state. This runs when data or measurements change.
1839
1836
  const { totalHeight, positions } = useMemo(() => {
1840
1837
  const shadowArray =
1841
1838
  getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
@@ -1857,7 +1854,6 @@ function createProxyHandler<T>(
1857
1854
  shadowUpdateTrigger,
1858
1855
  ]);
1859
1856
 
1860
- // Memoize the virtualized slice of data.
1861
1857
  const virtualState = useMemo(() => {
1862
1858
  const start = Math.max(0, range.startIndex);
1863
1859
  const end = Math.min(totalCount, range.endIndex);
@@ -1871,37 +1867,11 @@ function createProxyHandler<T>(
1871
1867
  validIndices,
1872
1868
  });
1873
1869
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1874
- useEffect(() => {
1875
- if (stickToBottom && totalCount > 0 && containerRef.current) {
1876
- // When count increases, immediately adjust range to show bottom
1877
- const container = containerRef.current;
1878
- const visibleCount = Math.ceil(
1879
- container.clientHeight / itemHeight
1880
- );
1881
-
1882
- // Set range to show the last items including the new one
1883
- setRange({
1884
- startIndex: Math.max(
1885
- 0,
1886
- totalCount - visibleCount - overscan
1887
- ),
1888
- endIndex: totalCount,
1889
- });
1890
1870
 
1891
- // Then scroll to bottom after a short delay
1892
- setTimeout(() => {
1893
- container.scrollTop = container.scrollHeight;
1894
- }, 100);
1895
- }
1896
- }, [totalCount]);
1897
- // This is the main effect that handles all scrolling and updates.
1898
1871
  useLayoutEffect(() => {
1899
1872
  const container = containerRef.current;
1900
1873
  if (!container) return;
1901
1874
 
1902
- let scrollTimeoutId: NodeJS.Timeout;
1903
-
1904
- // This function determines what's visible in the viewport.
1905
1875
  const updateVirtualRange = () => {
1906
1876
  if (!container) return;
1907
1877
  const { scrollTop } = container;
@@ -1925,7 +1895,6 @@ function createProxyHandler<T>(
1925
1895
  setRange({ startIndex, endIndex });
1926
1896
  };
1927
1897
 
1928
- // This function handles ONLY user-initiated scrolls.
1929
1898
  const handleUserScroll = () => {
1930
1899
  isLockedToBottomRef.current =
1931
1900
  container.scrollHeight -
@@ -1938,31 +1907,39 @@ function createProxyHandler<T>(
1938
1907
  container.addEventListener("scroll", handleUserScroll, {
1939
1908
  passive: true,
1940
1909
  });
1941
-
1942
- // --- THE CORE FIX ---
1943
- if (stickToBottom && isLockedToBottomRef.current) {
1944
- // We use a timeout to wait for React to render AND for useMeasure to update heights.
1945
- // This is the CRUCIAL part that fixes the race condition.
1946
- scrollTimeoutId = setTimeout(() => {
1947
- console.log("totalHeight", totalHeight);
1948
- if (isLockedToBottomRef.current) {
1910
+ console.log("totalCount", totalCount);
1911
+ // Handle scrolling
1912
+ if (stickToBottom && totalCount > 0) {
1913
+ const isInitialLoad =
1914
+ lastTotalCountRef.current === 0 && totalCount > 0;
1915
+ const hasNewItems = totalCount > lastTotalCountRef.current;
1916
+ console.log("isInitialLoad", isInitialLoad);
1917
+ console.log("hasNewItems", hasNewItems);
1918
+ if (isInitialLoad) {
1919
+ // First load - always scroll to bottom
1920
+ setTimeout(() => {
1921
+ container.scrollTop = container.scrollHeight;
1922
+ isLockedToBottomRef.current = true;
1923
+ }, 1000); // Longer delay for initial load
1924
+ } else if (hasNewItems && isLockedToBottomRef.current) {
1925
+ // New items added and user is at bottom - smooth scroll
1926
+ setTimeout(() => {
1949
1927
  container.scrollTo({
1950
- top: 999999999,
1951
- behavior: "smooth", // ALWAYS 'auto' for an instant, correct jump.
1928
+ top: container.scrollHeight,
1929
+ behavior: "smooth",
1952
1930
  });
1953
- }
1954
- }, 200); // A small 50ms delay is a robust buffer.
1931
+ }, 100);
1932
+ }
1933
+ // If user has scrolled up, don't auto-scroll
1955
1934
  }
1956
1935
 
1957
1936
  updateVirtualRange();
1937
+ lastTotalCountRef.current = totalCount;
1958
1938
 
1959
- // Cleanup function is vital to prevent memory leaks.
1960
1939
  return () => {
1961
- clearTimeout(scrollTimeoutId);
1962
1940
  container.removeEventListener("scroll", handleUserScroll);
1963
1941
  };
1964
- // This effect re-runs whenever the list size or item heights change.
1965
- }, [totalCount, positions, totalHeight, stickToBottom]);
1942
+ }, [totalCount, positions, stickToBottom]);
1966
1943
 
1967
1944
  const scrollToBottom = useCallback(
1968
1945
  (behavior: ScrollBehavior = "smooth") => {