cogsbox-state 0.5.303 → 0.5.305

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.303",
3
+ "version": "0.5.305",
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,28 +1814,10 @@ function createProxyHandler<T>(
1814
1814
  endIndex: 10,
1815
1815
  });
1816
1816
 
1817
- const [heightsVersion, setHeightsVersion] = useState(0);
1818
- const forceRecalculate = useCallback(
1819
- () => setHeightsVersion((v) => v + 1),
1820
- []
1821
- );
1822
-
1823
- // Track scroll position
1824
1817
  const isAtBottomRef = useRef(stickToBottom);
1825
1818
  const previousTotalCountRef = useRef(0);
1826
1819
  const isInitialMountRef = useRef(true);
1827
- const hasScrolledToBottomRef = useRef(false); // Track if we've done initial scroll
1828
-
1829
- useEffect(() => {
1830
- const unsubscribe = getGlobalStore
1831
- .getState()
1832
- .subscribeToShadowState(stateKey, forceRecalculate);
1833
- const timer = setTimeout(forceRecalculate, 50);
1834
- return () => {
1835
- unsubscribe();
1836
- clearTimeout(timer);
1837
- };
1838
- }, [stateKey, forceRecalculate]);
1820
+ const hasMeasurementsRef = useRef(false);
1839
1821
 
1840
1822
  const sourceArray = getGlobalStore().getNestedState(
1841
1823
  stateKey,
@@ -1843,32 +1825,26 @@ function createProxyHandler<T>(
1843
1825
  ) as any[];
1844
1826
  const totalCount = sourceArray.length;
1845
1827
 
1846
- const { totalHeight, positions, allItemsMeasured } =
1847
- useMemo(() => {
1848
- const shadowArray =
1849
- getGlobalStore
1850
- .getState()
1851
- .getShadowMetadata(stateKey, path) || [];
1852
- let height = 0;
1853
- const pos: number[] = [];
1854
- let measured = true;
1855
-
1856
- for (let i = 0; i < totalCount; i++) {
1857
- pos[i] = height;
1858
- const measuredHeight =
1859
- shadowArray[i]?.virtualizer?.itemHeight;
1860
- if (!measuredHeight && totalCount > 0) {
1861
- measured = false;
1862
- }
1863
- height += measuredHeight || itemHeight;
1864
- }
1828
+ // Calculate heights from shadow state
1829
+ const { totalHeight, positions } = useMemo(() => {
1830
+ const shadowArray =
1831
+ getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
1832
+ [];
1833
+ let height = 0;
1834
+ const pos: number[] = [];
1835
+ let hasMeasurements = false;
1836
+
1837
+ for (let i = 0; i < totalCount; i++) {
1838
+ pos[i] = height;
1839
+ const measuredHeight =
1840
+ shadowArray[i]?.virtualizer?.itemHeight;
1841
+ if (measuredHeight) hasMeasurements = true;
1842
+ height += measuredHeight || itemHeight;
1843
+ }
1865
1844
 
1866
- return {
1867
- totalHeight: height,
1868
- positions: pos,
1869
- allItemsMeasured: measured,
1870
- };
1871
- }, [totalCount, stateKey, path, itemHeight, heightsVersion]);
1845
+ hasMeasurementsRef.current = hasMeasurements;
1846
+ return { totalHeight: height, positions: pos };
1847
+ }, [totalCount, stateKey, path.join("."), itemHeight]);
1872
1848
 
1873
1849
  const virtualState = useMemo(() => {
1874
1850
  const start = Math.max(0, range.startIndex);
@@ -1894,7 +1870,6 @@ function createProxyHandler<T>(
1894
1870
 
1895
1871
  const handleScroll = () => {
1896
1872
  const { scrollTop, clientHeight, scrollHeight } = container;
1897
- // Consider "at bottom" if within 10px
1898
1873
  isAtBottomRef.current =
1899
1874
  scrollHeight - scrollTop - clientHeight < 10;
1900
1875
 
@@ -1938,39 +1913,9 @@ function createProxyHandler<T>(
1938
1913
  });
1939
1914
 
1940
1915
  // Handle stick to bottom
1941
- if (stickToBottom) {
1942
- if (
1943
- isInitialMountRef.current &&
1944
- !hasScrolledToBottomRef.current
1945
- ) {
1946
- // For initial mount, wait for items to be measured
1947
- if (allItemsMeasured && totalCount > 0) {
1948
- container.scrollTo({
1949
- top: container.scrollHeight,
1950
- behavior: "auto",
1951
- });
1952
- hasScrolledToBottomRef.current = true;
1953
- isInitialMountRef.current = false;
1954
- } else if (totalCount > 0) {
1955
- // If not all measured yet, try again soon
1956
- const retryTimer = setTimeout(() => {
1957
- if (containerRef.current && isInitialMountRef.current) {
1958
- containerRef.current.scrollTo({
1959
- top: containerRef.current.scrollHeight,
1960
- behavior: "auto",
1961
- });
1962
- hasScrolledToBottomRef.current = true;
1963
- isInitialMountRef.current = false;
1964
- }
1965
- }, 100);
1966
- return () => clearTimeout(retryTimer);
1967
- }
1968
- } else if (
1969
- !isInitialMountRef.current &&
1970
- wasAtBottom &&
1971
- listGrew
1972
- ) {
1973
- // New items added and we were at bottom - stay at bottom
1916
+ if (stickToBottom && !isInitialMountRef.current) {
1917
+ // Only auto-scroll for new items after initial mount
1918
+ if (wasAtBottom && listGrew) {
1974
1919
  requestAnimationFrame(() => {
1975
1920
  container.scrollTo({
1976
1921
  top: container.scrollHeight,
@@ -1978,8 +1923,6 @@ function createProxyHandler<T>(
1978
1923
  });
1979
1924
  });
1980
1925
  }
1981
- } else {
1982
- isInitialMountRef.current = false;
1983
1926
  }
1984
1927
 
1985
1928
  // Run handleScroll once to set initial range
@@ -1987,13 +1930,31 @@ function createProxyHandler<T>(
1987
1930
 
1988
1931
  return () =>
1989
1932
  container.removeEventListener("scroll", handleScroll);
1990
- }, [
1991
- totalCount,
1992
- positions,
1993
- overscan,
1994
- stickToBottom,
1995
- allItemsMeasured,
1996
- ]);
1933
+ }, [totalCount, positions, overscan, stickToBottom]);
1934
+
1935
+ // Separate effect for initial scroll to bottom
1936
+ useEffect(() => {
1937
+ if (
1938
+ stickToBottom &&
1939
+ isInitialMountRef.current &&
1940
+ totalCount > 0 &&
1941
+ hasMeasurementsRef.current
1942
+ ) {
1943
+ const container = containerRef.current;
1944
+ if (container) {
1945
+ // Use rAF to ensure DOM is updated
1946
+ requestAnimationFrame(() => {
1947
+ requestAnimationFrame(() => {
1948
+ container.scrollTo({
1949
+ top: container.scrollHeight,
1950
+ behavior: "auto",
1951
+ });
1952
+ isInitialMountRef.current = false;
1953
+ });
1954
+ });
1955
+ }
1956
+ }
1957
+ }, [stickToBottom, totalCount, positions]); // positions change triggers this when measurements come in
1997
1958
 
1998
1959
  const scrollToBottom = useCallback(
1999
1960
  (behavior: ScrollBehavior = "smooth") => {