cogsbox-state 0.5.413 → 0.5.415

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.413",
3
+ "version": "0.5.415",
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
@@ -1823,7 +1823,6 @@ function createProxyHandler<T>(
1823
1823
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1824
1824
  const wasAtBottomRef = useRef(false);
1825
1825
  const previousCountRef = useRef(0);
1826
- const hasInitializedRef = useRef(false); // Track if we've done initial scroll
1827
1826
 
1828
1827
  // Subscribe to shadow state updates
1829
1828
  useEffect(() => {
@@ -1833,7 +1832,7 @@ function createProxyHandler<T>(
1833
1832
  setShadowUpdateTrigger((prev) => prev + 1);
1834
1833
  });
1835
1834
  return unsubscribe;
1836
- }, [stateKey]);
1835
+ }, []); // Empty deps - stateKey never changes
1837
1836
 
1838
1837
  const sourceArray = getGlobalStore().getNestedState(
1839
1838
  stateKey,
@@ -1855,13 +1854,7 @@ function createProxyHandler<T>(
1855
1854
  height += measuredHeight || itemHeight;
1856
1855
  }
1857
1856
  return { totalHeight: height, positions: pos };
1858
- }, [
1859
- totalCount,
1860
- stateKey,
1861
- path.join("."),
1862
- itemHeight,
1863
- shadowUpdateTrigger,
1864
- ]);
1857
+ }, [totalCount, itemHeight, shadowUpdateTrigger]);
1865
1858
 
1866
1859
  // Create virtual state
1867
1860
  const virtualState = useMemo(() => {
@@ -1876,47 +1869,26 @@ function createProxyHandler<T>(
1876
1869
  ...meta,
1877
1870
  validIndices,
1878
1871
  });
1879
- }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1880
-
1881
- // Helper to scroll to last item using stored ref
1882
- const scrollToLastItem = useCallback(() => {
1883
- const shadowArray =
1884
- getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
1885
- [];
1886
- const lastIndex = totalCount - 1;
1887
-
1888
- if (lastIndex >= 0) {
1889
- const lastItemData = shadowArray[lastIndex];
1890
- if (lastItemData?.virtualizer?.domRef) {
1891
- const element = lastItemData.virtualizer.domRef;
1892
- if (element && element.scrollIntoView) {
1893
- element.scrollIntoView({
1894
- behavior: "auto",
1895
- block: "end",
1896
- inline: "nearest",
1897
- });
1898
- return true;
1899
- }
1900
- }
1901
- }
1902
- return false;
1903
- }, [stateKey, path, totalCount]);
1872
+ }, [range.startIndex, range.endIndex, sourceArray]);
1904
1873
 
1905
- // Handle ONLY new items - not height changes
1874
+ // Handle new items when at bottom
1906
1875
  useEffect(() => {
1907
1876
  if (!stickToBottom || totalCount === 0) return;
1908
1877
 
1909
1878
  const hasNewItems = totalCount > previousCountRef.current;
1910
1879
 
1911
- // Initial load
1880
+ // Only scroll if we were at bottom AND new items arrived
1912
1881
  if (
1913
- previousCountRef.current === 0 &&
1914
- totalCount > 0 &&
1915
- !hasInitializedRef.current
1882
+ hasNewItems &&
1883
+ wasAtBottomRef.current &&
1884
+ previousCountRef.current > 0
1916
1885
  ) {
1917
- hasInitializedRef.current = true;
1886
+ const container = containerRef.current;
1887
+ if (!container) return;
1888
+
1889
+ // Update range to show end
1918
1890
  const visibleCount = Math.ceil(
1919
- (containerRef.current?.clientHeight || 600) / itemHeight
1891
+ container.clientHeight / itemHeight
1920
1892
  );
1921
1893
  setRange({
1922
1894
  startIndex: Math.max(
@@ -1926,36 +1898,14 @@ function createProxyHandler<T>(
1926
1898
  endIndex: totalCount,
1927
1899
  });
1928
1900
 
1901
+ // Scroll to bottom after render
1929
1902
  setTimeout(() => {
1930
- if (containerRef.current) {
1931
- containerRef.current.scrollTop =
1932
- containerRef.current.scrollHeight;
1933
- wasAtBottomRef.current = true;
1934
- }
1935
- }, 100);
1936
- }
1937
- // New items added AFTER initial load
1938
- else if (hasNewItems && wasAtBottomRef.current) {
1939
- const visibleCount = Math.ceil(
1940
- (containerRef.current?.clientHeight || 600) / itemHeight
1941
- );
1942
- const newRange = {
1943
- startIndex: Math.max(
1944
- 0,
1945
- totalCount - visibleCount - overscan
1946
- ),
1947
- endIndex: totalCount,
1948
- };
1949
-
1950
- setRange(newRange);
1951
-
1952
- setTimeout(() => {
1953
- scrollToLastItem();
1903
+ container.scrollTop = container.scrollHeight;
1954
1904
  }, 50);
1955
1905
  }
1956
1906
 
1957
1907
  previousCountRef.current = totalCount;
1958
- }, [totalCount]); // ONLY depend on totalCount, not positions!
1908
+ }, [totalCount]); // ONLY totalCount - nothing else matters
1959
1909
 
1960
1910
  // Handle scroll events
1961
1911
  useEffect(() => {
@@ -1964,74 +1914,42 @@ function createProxyHandler<T>(
1964
1914
 
1965
1915
  const handleScroll = () => {
1966
1916
  const { scrollTop, scrollHeight, clientHeight } = container;
1967
- const distanceFromBottom =
1968
- scrollHeight - scrollTop - clientHeight;
1969
-
1970
- // Track if we're at bottom
1971
- wasAtBottomRef.current = distanceFromBottom < 100;
1972
-
1973
- // Update visible range based on scroll position
1974
- let startIndex = 0;
1975
- for (let i = 0; i < positions.length; i++) {
1976
- if (positions[i]! > scrollTop - itemHeight * overscan) {
1977
- startIndex = Math.max(0, i - 1);
1978
- break;
1979
- }
1980
- }
1981
1917
 
1982
- let endIndex = startIndex;
1983
- const viewportEnd = scrollTop + clientHeight;
1984
- for (let i = startIndex; i < positions.length; i++) {
1985
- if (positions[i]! > viewportEnd + itemHeight * overscan) {
1986
- break;
1987
- }
1988
- endIndex = i;
1989
- }
1918
+ // Check if at bottom
1919
+ wasAtBottomRef.current =
1920
+ scrollHeight - scrollTop - clientHeight < 100;
1921
+
1922
+ // Calculate visible range
1923
+ const firstVisible = Math.floor(scrollTop / itemHeight);
1924
+ const lastVisible = Math.ceil(
1925
+ (scrollTop + clientHeight) / itemHeight
1926
+ );
1990
1927
 
1991
1928
  setRange({
1992
- startIndex: Math.max(0, startIndex),
1993
- endIndex: Math.min(totalCount, endIndex + 1 + overscan),
1929
+ startIndex: Math.max(0, firstVisible - overscan),
1930
+ endIndex: Math.min(totalCount, lastVisible + overscan),
1994
1931
  });
1995
1932
  };
1996
1933
 
1997
1934
  container.addEventListener("scroll", handleScroll, {
1998
1935
  passive: true,
1999
1936
  });
2000
-
2001
- // Just calculate visible range, no scrolling
2002
1937
  handleScroll();
2003
1938
 
2004
- return () => {
1939
+ return () =>
2005
1940
  container.removeEventListener("scroll", handleScroll);
2006
- };
2007
- }, [positions, totalCount, itemHeight, overscan]);
1941
+ }, [positions]); // Only re-attach when positions change
2008
1942
 
2009
1943
  const scrollToBottom = useCallback(() => {
2010
- wasAtBottomRef.current = true;
2011
- const scrolled = scrollToLastItem();
2012
- if (!scrolled && containerRef.current) {
1944
+ if (containerRef.current) {
2013
1945
  containerRef.current.scrollTop =
2014
1946
  containerRef.current.scrollHeight;
1947
+ wasAtBottomRef.current = true;
2015
1948
  }
2016
- }, [scrollToLastItem]);
1949
+ }, []);
2017
1950
 
2018
1951
  const scrollToIndex = useCallback(
2019
1952
  (index: number, behavior: ScrollBehavior = "smooth") => {
2020
- const shadowArray =
2021
- getGlobalStore
2022
- .getState()
2023
- .getShadowMetadata(stateKey, path) || [];
2024
- const itemData = shadowArray[index];
2025
-
2026
- if (itemData?.virtualizer?.domRef) {
2027
- const element = itemData.virtualizer.domRef;
2028
- if (element && element.scrollIntoView) {
2029
- element.scrollIntoView({ behavior, block: "center" });
2030
- return;
2031
- }
2032
- }
2033
-
2034
- // Fallback to position-based scrolling
2035
1953
  if (containerRef.current && positions[index] !== undefined) {
2036
1954
  containerRef.current.scrollTo({
2037
1955
  top: positions[index],
@@ -2039,7 +1957,7 @@ function createProxyHandler<T>(
2039
1957
  });
2040
1958
  }
2041
1959
  },
2042
- [positions, stateKey, path]
1960
+ [positions]
2043
1961
  );
2044
1962
 
2045
1963
  const virtualizerProps = {